From f7e710202eb15bb89056bcfb5fcd222a065ba8fb Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Wed, 6 Jan 2021 23:37:11 +1300 Subject: [PATCH 01/14] Port Tray App to .NET 5. --- DnsServerSystemTrayApp/App.config | 6 -- .../DnsServerSystemTrayApp.csproj | 83 +++---------------- .../Properties/AssemblyInfo.cs | 36 -------- 3 files changed, 13 insertions(+), 112 deletions(-) delete mode 100644 DnsServerSystemTrayApp/App.config delete mode 100644 DnsServerSystemTrayApp/Properties/AssemblyInfo.cs diff --git a/DnsServerSystemTrayApp/App.config b/DnsServerSystemTrayApp/App.config deleted file mode 100644 index 4bfa0056..00000000 --- a/DnsServerSystemTrayApp/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/DnsServerSystemTrayApp/DnsServerSystemTrayApp.csproj b/DnsServerSystemTrayApp/DnsServerSystemTrayApp.csproj index 273993b9..af911783 100644 --- a/DnsServerSystemTrayApp/DnsServerSystemTrayApp.csproj +++ b/DnsServerSystemTrayApp/DnsServerSystemTrayApp.csproj @@ -1,48 +1,17 @@ - - - + + - Debug - AnyCPU - {2F91BD07-2CEE-47FA-8486-457B54612B4C} WinExe + net5.0-windows + true + true DnsServerSystemTrayApp DnsServerSystemTrayApp - v4.8 - 512 - false - true - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - + Shreyas Zare logo2.ico + 2.0.0.0 - - - - - False ..\..\TechnitiumLibrary\bin\TechnitiumLibrary.IO.dll @@ -52,45 +21,16 @@ - - - Form - - - frmAbout.cs - - - Form - - - frmManageDnsProviders.cs - - - - - - - frmAbout.cs - - - frmManageDnsProviders.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - + True Resources.resx True - SettingsSingleFileGenerator Settings.Designer.cs - + True Settings.settings True @@ -99,5 +39,8 @@ - + + + + \ No newline at end of file diff --git a/DnsServerSystemTrayApp/Properties/AssemblyInfo.cs b/DnsServerSystemTrayApp/Properties/AssemblyInfo.cs deleted file mode 100644 index 6b1e9ec2..00000000 --- a/DnsServerSystemTrayApp/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Technitium DNS Server")] -[assembly: AssemblyDescription("Technitium DNS Server")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Technitium")] -[assembly: AssemblyProduct("Technitium DNS Server")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("2f91bd07-2cee-47fa-8486-457b54612b4c")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// 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.3.2.0")] -[assembly: AssemblyFileVersion("1.3.2.0")] From 227093637404d90269d0bad534a0d97293590b90 Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Wed, 6 Jan 2021 23:38:59 +1300 Subject: [PATCH 02/14] Port DNS Service to .NET 5. --- DnsService/DnsService.csproj | 77 +++----------- DnsService/ProjectInstaller.Designer.cs | 60 ----------- DnsService/ProjectInstaller.cs | 90 ----------------- DnsService/ProjectInstaller.resx | 129 ------------------------ DnsService/Properties/AssemblyInfo.cs | 36 ------- 5 files changed, 12 insertions(+), 380 deletions(-) delete mode 100644 DnsService/ProjectInstaller.Designer.cs delete mode 100644 DnsService/ProjectInstaller.cs delete mode 100644 DnsService/ProjectInstaller.resx delete mode 100644 DnsService/Properties/AssemblyInfo.cs diff --git a/DnsService/DnsService.csproj b/DnsService/DnsService.csproj index 1fec5bf9..10c6cb8a 100644 --- a/DnsService/DnsService.csproj +++ b/DnsService/DnsService.csproj @@ -1,83 +1,30 @@ - - - + + - Debug - AnyCPU - {7873B2B8-01BA-48BC-B4B0-0857FFD873C9} WinExe + net5.0-windows7.0 + true + DnsService DnsService - v4.8 - 512 - false - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - AnyCPU - none - true - bin\Release\ - TRACE - prompt - 4 - false - - logo2.ico + 2.6.0.0 - - - ..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.Firewall.dll - - - Component - - - ProjectInstaller.cs - - - Component - - - DnsService.cs - - - - - - - ProjectInstaller.cs - - - DnsService.cs - - - - - - {4494b79b-588c-41f2-95ad-0897123af154} DnsServerCore - + + + + + + \ No newline at end of file diff --git a/DnsService/ProjectInstaller.Designer.cs b/DnsService/ProjectInstaller.Designer.cs deleted file mode 100644 index 2fe7732f..00000000 --- a/DnsService/ProjectInstaller.Designer.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace DnsService -{ - partial class ProjectInstaller - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller(); - this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller(); - // - // serviceProcessInstaller1 - // - this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem; - this.serviceProcessInstaller1.Password = null; - this.serviceProcessInstaller1.Username = null; - // - // serviceInstaller1 - // - this.serviceInstaller1.Description = "Technitium DNS Server"; - this.serviceInstaller1.DisplayName = "Technitium DNS Server"; - this.serviceInstaller1.ServiceName = "DnsService"; - this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic; - // - // ProjectInstaller - // - this.Installers.AddRange(new System.Configuration.Install.Installer[] { - this.serviceProcessInstaller1, - this.serviceInstaller1}); - - } - - #endregion - - private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1; - private System.ServiceProcess.ServiceInstaller serviceInstaller1; - } -} \ No newline at end of file diff --git a/DnsService/ProjectInstaller.cs b/DnsService/ProjectInstaller.cs deleted file mode 100644 index fa6da0c8..00000000 --- a/DnsService/ProjectInstaller.cs +++ /dev/null @@ -1,90 +0,0 @@ -/* -Technitium Library -Copyright (C) 2018 Shreyas Zare (shreyas@technitium.com) - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -*/ - -using System.Collections; -using System.ComponentModel; -using System.Configuration.Install; -using System.ServiceProcess; - -namespace DnsService -{ - [RunInstaller(true)] - public partial class ProjectInstaller : Installer - { - public ProjectInstaller() - { - InitializeComponent(); - } - - protected override void OnBeforeInstall(IDictionary savedState) - { - try - { - foreach (ServiceController sc in ServiceController.GetServices()) - { - if (sc.ServiceName == serviceInstaller1.ServiceName) - { - //found previously installed service - //stop service - if (sc.Status == ServiceControllerStatus.Running) - sc.Stop(); - - //uninstall service - using (ServiceInstaller si = new ServiceInstaller()) - { - si.Context = new InstallContext(); - si.ServiceName = serviceInstaller1.ServiceName; - si.Uninstall(null); - } - - break; - } - } - } - catch - { } - } - - protected override void OnAfterInstall(IDictionary savedState) - { - try - { - using (ServiceController sc = new ServiceController(serviceInstaller1.ServiceName)) - { - sc.Start(); - } - } - catch - { } - } - - protected override void OnBeforeUninstall(IDictionary savedState) - { - try - { - using (ServiceController sc = new ServiceController(serviceInstaller1.ServiceName)) - { - sc.Stop(); - } - } - catch - { } - } - } -} diff --git a/DnsService/ProjectInstaller.resx b/DnsService/ProjectInstaller.resx deleted file mode 100644 index 235f1b0b..00000000 --- a/DnsService/ProjectInstaller.resx +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 56 - - - 196, 17 - - - False - - \ No newline at end of file diff --git a/DnsService/Properties/AssemblyInfo.cs b/DnsService/Properties/AssemblyInfo.cs deleted file mode 100644 index 2bd1169f..00000000 --- a/DnsService/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Technitium DNS Server")] -[assembly: AssemblyDescription("Technitium DNS Server")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Technitium")] -[assembly: AssemblyProduct("Technitium DNS Server")] -[assembly: AssemblyCopyright("Copyright © 2021")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("7873b2b8-01ba-48bc-b4b0-0857ffd873c9")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// 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("5.6.0.0")] -[assembly: AssemblyFileVersion("5.6.0.0")] From 594e3ac89791c973e9605ab9fa14fe8e36829935 Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Wed, 6 Jan 2021 23:40:41 +1300 Subject: [PATCH 03/14] Create Inno Setup installer scripts. --- DnsServiceSetup/Windows/DnsServiceSetup.iss | 51 +++ DnsServiceSetup/Windows/DnsServiceSetup.pas | 66 ++++ .../Windows/depend/isxdl/chinese.ini | 49 +++ .../Windows/depend/isxdl/czech.ini | 53 +++ .../Windows/depend/isxdl/dutch.ini | 49 +++ .../Windows/depend/isxdl/english.ini | 49 +++ .../Windows/depend/isxdl/french.ini | 45 +++ .../Windows/depend/isxdl/french2.ini | 45 +++ .../Windows/depend/isxdl/french3.ini | 46 +++ .../Windows/depend/isxdl/german.ini | 49 +++ .../Windows/depend/isxdl/isxdl.dll | Bin 0 -> 124416 bytes .../Windows/depend/isxdl/isxdl.iss | 14 + .../Windows/depend/isxdl/italian.ini | 49 +++ .../Windows/depend/isxdl/japanese.ini | 49 +++ .../Windows/depend/isxdl/korean.ini | 49 +++ .../Windows/depend/isxdl/norwegian.ini | 47 +++ .../Windows/depend/isxdl/polish.ini | 45 +++ .../Windows/depend/isxdl/portugues.ini | 45 +++ .../Windows/depend/isxdl/portuguese.ini | 46 +++ .../Windows/depend/isxdl/russian.ini | 49 +++ .../Windows/depend/isxdl/spanish.ini | 46 +++ .../Windows/depend/isxdl/swedish.ini | 48 +++ .../Windows/depend/lang/chinese.iss | 19 + DnsServiceSetup/Windows/depend/lang/dutch.iss | 19 + .../Windows/depend/lang/english.iss | 18 + .../Windows/depend/lang/french.iss | 19 + .../Windows/depend/lang/german.iss | 19 + .../Windows/depend/lang/italian.iss | 19 + .../Windows/depend/lang/japanese.iss | 19 + .../Windows/depend/lang/polish.iss | 19 + .../Windows/depend/lang/russian.iss | 19 + DnsServiceSetup/Windows/depend/products.iss | 6 + DnsServiceSetup/Windows/depend/products.pas | 338 ++++++++++++++++++ .../Windows/depend/products/dotnet5.iss | 35 ++ .../Windows/depend/products/dotnet5.pas | 160 +++++++++ .../Windows/depend/products/dotnetfw4.iss | 9 + .../Windows/depend/products/dotnetfw4.pas | 40 +++ DnsServiceSetup/Windows/service.pas | 224 ++++++++++++ 38 files changed, 1971 insertions(+) create mode 100644 DnsServiceSetup/Windows/DnsServiceSetup.iss create mode 100644 DnsServiceSetup/Windows/DnsServiceSetup.pas create mode 100644 DnsServiceSetup/Windows/depend/isxdl/chinese.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/czech.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/dutch.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/english.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/french.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/french2.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/french3.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/german.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/isxdl.dll create mode 100644 DnsServiceSetup/Windows/depend/isxdl/isxdl.iss create mode 100644 DnsServiceSetup/Windows/depend/isxdl/italian.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/japanese.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/korean.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/norwegian.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/polish.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/portugues.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/portuguese.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/russian.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/spanish.ini create mode 100644 DnsServiceSetup/Windows/depend/isxdl/swedish.ini create mode 100644 DnsServiceSetup/Windows/depend/lang/chinese.iss create mode 100644 DnsServiceSetup/Windows/depend/lang/dutch.iss create mode 100644 DnsServiceSetup/Windows/depend/lang/english.iss create mode 100644 DnsServiceSetup/Windows/depend/lang/french.iss create mode 100644 DnsServiceSetup/Windows/depend/lang/german.iss create mode 100644 DnsServiceSetup/Windows/depend/lang/italian.iss create mode 100644 DnsServiceSetup/Windows/depend/lang/japanese.iss create mode 100644 DnsServiceSetup/Windows/depend/lang/polish.iss create mode 100644 DnsServiceSetup/Windows/depend/lang/russian.iss create mode 100644 DnsServiceSetup/Windows/depend/products.iss create mode 100644 DnsServiceSetup/Windows/depend/products.pas create mode 100644 DnsServiceSetup/Windows/depend/products/dotnet5.iss create mode 100644 DnsServiceSetup/Windows/depend/products/dotnet5.pas create mode 100644 DnsServiceSetup/Windows/depend/products/dotnetfw4.iss create mode 100644 DnsServiceSetup/Windows/depend/products/dotnetfw4.pas create mode 100644 DnsServiceSetup/Windows/service.pas diff --git a/DnsServiceSetup/Windows/DnsServiceSetup.iss b/DnsServiceSetup/Windows/DnsServiceSetup.iss new file mode 100644 index 00000000..38bd1ea6 --- /dev/null +++ b/DnsServiceSetup/Windows/DnsServiceSetup.iss @@ -0,0 +1,51 @@ +#define PRODUCT_NAME "DNS Server" +#define APPID "{{9B86AC7F-53B3-4E31-B245-D4602D16F5C8}" +#define PRODUCT_VERSION "5.6" +#define COMPANY "Technitium" +#define TITLE "Technitium DNS Server" +#define FILES_LOCATION "..\..\DnsService\bin\Release" +#define TRAYAPP_LOCATION "..\..\DnsServerSystemTrayApp\obj\Release" + +[Setup] +PrivilegesRequired=admin +AppName={#TITLE} +AppVersion={#PRODUCT_VERSION} +AppId={#APPID} +DefaultDirName={commonpf}\{#COMPANY}\{#PRODUCT_NAME} +DefaultGroupName={#COMPANY} +DisableProgramGroupPage=yes +AppCopyright=Copyright (c) 2021 {#COMPANY} +AppPublisher={#COMPANY} +OutputDir=..\Release +OutputBaseFilename=DnsServiceSetup +Compression=lzma2/max + +[Files] +Source: "{#TRAYAPP_LOCATION}\DnsServerSystemTrayApp.exe"; DestDir: "{app}"; +Source: "{#FILES_LOCATION}\*.*"; Excludes: "*.pdb,DnsService.exe"; DestDir: "{app}"; Flags: recursesubdirs; +Source: "{#FILES_LOCATION}\DnsService.exe"; DestDir: "{app}"; Flags: recursesubdirs; BeforeInstall: DoRemoveService; AfterInstall: DoInstallService; + +[Tasks] +Name: "desktopicon"; Description: "Create an icon on the &desktop"; + +[CustomMessages] +ServiceName=DnsService +ServiceDisplayName=Technitium DNS Server +ServiceInstallFailure=The DNS Service could not be installed. %1 +ServiceManagerUnavailable=The Service Manager is not available! +DependenciesDir=. + +[Registry] +Root: HKLM; Subkey: "Software\{#COMPANY}"; Flags: uninsdeletekeyifempty +Root: HKCU; Subkey: "Software\{#COMPANY}"; Flags: uninsdeletekeyifempty + +[Icons] +Name: "{userprograms}\Technitium DNS Server"; Comment: "DNS Server Tray App"; Filename: "{app}\DnsServerSystemTrayApp.exe"; WorkingDir: "{app}\"; Flags: createonlyiffileexists +Name: "{userdesktop}\Technitium DNS Server"; Filename: "{app}\DnsServerSystemTrayApp.exe"; WorkingDir: "{app}\"; Flags: createonlyiffileexists; Tasks: desktopicon + +#include "depend\lang\english.iss" +#include "depend\products.iss" +#include "depend\products\dotnet5.iss" + +[Code] +#include "DnsServiceSetup.pas" \ No newline at end of file diff --git a/DnsServiceSetup/Windows/DnsServiceSetup.pas b/DnsServiceSetup/Windows/DnsServiceSetup.pas new file mode 100644 index 00000000..33c25110 --- /dev/null +++ b/DnsServiceSetup/Windows/DnsServiceSetup.pas @@ -0,0 +1,66 @@ +#include "service.pas" +//Include the sc functionality + +function IsUpgrade: Boolean; //Check to see if the install is an upgrade +var + Value: string; + UninstallKey: string; +begin + UninstallKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + + ExpandConstant('{#SetupSetting("AppId")}') + '_is1'; + Result := (RegQueryStringValue(HKLM, UninstallKey, 'UninstallString', Value) or + RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and (Value <> ''); +end; + +//Skips the Task selection screen if an upgrade install +function ShouldSkipPage(PageID: Integer): Boolean; +begin + Result := (PageID = wpSelectTasks) and IsUpgrade; +end; + +function InitializeSetup(): boolean; +begin + //Specify the dependencies to install here + dotnet_5_desktop(); + Result := true; +end; + +procedure DoRemoveService(); //Removes the dns service from the scm +begin + if IsServiceInstalled(ExpandConstant('{cm:ServiceName}')) then begin + Log('Service: Already installed'); + if IsServiceRunning(ExpandConstant('{cm:ServiceName}')) then begin + Log('Service: Already running'); + StopService(ExpandConstant('{cm:ServiceName}')); + Sleep(5000); + end; + + Log('Service: Remove'); + RemoveService(ExpandConstant('{cm:ServiceName}')) + end; +end; + +procedure DoInstallService(); //Adds the dns service to the scm +var + InstallSuccess: Boolean; + MsgResult: Integer; +begin + Log('Service: Begin Install'); + InstallSuccess := InstallService(ExpandConstant('{app}\DnsService.exe'), ExpandConstant('{cm:ServiceName}'), ExpandConstant('{cm:ServiceDisplayName}'), ExpandConstant('{cm:ServiceDisplayName}'), SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START); + if not InstallSuccess then + begin + Log('Service: Install Fail ' + ServiceErrorToMessage(GetLastError())); + SuppressibleMsgBox(ExpandConstant('{cm:ServiceInstallFailure,' + ServiceErrorToMessage(GetLastError()) + '}'), mbCriticalError, MB_OK, IDOK); + end else begin + Log('Service: Install Success, Starting'); + StartService(ExpandConstant('{cm:ServiceName}')); + end; +end; + +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +begin + if CurUninstallStep = usUninstall then + begin + DoRemoveService(); + end; +end; \ No newline at end of file diff --git a/DnsServiceSetup/Windows/depend/isxdl/chinese.ini b/DnsServiceSetup/Windows/depend/isxdl/chinese.ini new file mode 100644 index 00000000..6bbb632d --- /dev/null +++ b/DnsServiceSetup/Windows/depend/isxdl/chinese.ini @@ -0,0 +1,49 @@ +[strings] +; General +100=ļ +101=Ҫȡ +102=%1 (%2 / %3) +103=%1 KB +104=%1 KB / %2 KB (%3%) + +; Status information +110=ȡļϢ +111=ض %1 +112=ڷ... +113=Ӧ %1 +114=ӵ %1 +115=... +116=ӵ %1 + +; Error messages +120=Ӵ\n\n%1 +121=ʳ %1.\n\nص״̬Ϊ %2. +122=URL ȡ.\n\n%1 +123=ļд %1.\n\n%2 +124=ļ򿪴 %1.\n\n%2 +125='%1' Ч URL. +126=򿪳 %1.\n\n%2 +127=.\n\n%1 +128=ֵ֧Э. ֻ֧ HTTP FTP . +129=޷ӵ %1.\n\n%2 +130=ѯ״̬ʧ.\n\n%1 +131=ļ.\n\n%1 + +; Other +144=... +146= +147=װظļ. + +; labels +160=ļ: +161=ٶ: +162=״̬: +163=ʱ: +164=ʣʱ: +165=ǰļ: +166=ܽ: +167=ȡ +168=ȷ +169=û +170=û: +171=: diff --git a/DnsServiceSetup/Windows/depend/isxdl/czech.ini b/DnsServiceSetup/Windows/depend/isxdl/czech.ini new file mode 100644 index 00000000..11ef1c51 --- /dev/null +++ b/DnsServiceSetup/Windows/depend/isxdl/czech.ini @@ -0,0 +1,53 @@ +[strings] + +; Translation (c) 2005 Martin Kozk (martin.kozak@openoffice.cz) + +; General +100=Staen souboru +101=Pejete si peruit stahovn? +102=%1 (%2 z %3) +103=%1 KB +104=%1 KB z %2 KB (%3%) + +; Status information +110=Zskvn informac o souboru... +111=Pesmrovn na %1 +112=Odesln poadavku... +113=Zpracovn %1 +114=Spojen s %1 navzno +115=Pijmn... +116=Pipojovn k %1 + +; Error messages +120=Chyba pi pipojovn k sti Internet.\n\n%1 +121=Chyba pi otevrn %1.\n\nServer nastavil nvratov kd %2. +122=Chyba pi ten URL.\n\n%1 +123=Chyba pi zpisu do souboru %1.\n\n%2 +124=Chyba pi otevrn souboru %1.\n\n%2 +125='%1' nen platn URL. +126=Chyba pi otevrn %1.\n\n%2 +127=Chyba pi zasln poadavku.\n\n%1 +128=Nepodporovan protokol. Podporovny jsou pouze protokoly HTTP a FTP. +129=Pokus o pipojen k %1 selhalo.\n\n%2 +130=Pokus o zskn nvratovho kdu serveru selhal.\n\n%1 +131=Chyba pi zadvn poadavku na soubor.\n\n%1 + +; Other +144=O knihovn... +146=Staen komponent +147=Prvodce instalac stahuje pdavn komponenty do vaeho potae. + +; labels +160=Soubor: +161=Penosov rychlost: +162=Stav: +163=Uplynul as: +164=Zbvajc as: +165=Zpracovvan soubor: +166=Celkov prbh: +167=Zruit +168=OK +169=Uivatelsk jmno a heslo +170=Uivatelsk jmno: +171=Heslo: + diff --git a/DnsServiceSetup/Windows/depend/isxdl/dutch.ini b/DnsServiceSetup/Windows/depend/isxdl/dutch.ini new file mode 100644 index 00000000..6fe8ca75 --- /dev/null +++ b/DnsServiceSetup/Windows/depend/isxdl/dutch.ini @@ -0,0 +1,49 @@ +[strings] +; Algemeen +100=Bestand downloaden +101=Wilt u de download annuleren? +102=1% (%2 van %3) +103=%1 KB +104=%1 KB van %2 KB (%3%) + +; Status informatie +110=Bestandsinformatie ophalen ... +111=omgeleid naar %1 +112=Verzoek verzenden ... +113=oplossen %1 +114=Verbonden met 1% +115=Het ontvangen ... +116=Verbinden met %1 + +; foutmeldingen +120=Fout bij het verbinden met Internet. \n\n%1 +121=Fout bij het openen van %1.\n\nDe server terug statuscode %2. +122=Fout bij het lezen URL.\n\n%1 +123=Fout bij het schrijven bestand %1.\n\n%2 +124=Fout bij openen bestand %1.\n\n%2 +125='%1' is een ongeldige URL. +126=Fout bij openen %1.\n\n%2 +127=Fout bij het verzenden verzoek.\n\n%1 +128=Niet ondersteund protocol. Alleen HTTP en FTP-protocollen worden ondersteund. +129=Kan geen verbinding maken %1.\n\n%2 +130=Kan de status code opvragen.\n\n%1 +131=Fout bij het aanvragen van het bestand.\n\n%1 + +; anders +144=Over ... +146=Download +147=Setup is nu het downloaden van extra bestanden naar uw computer. + +; etiket +160=Bestand: +161=Speed: +162=Status: +163=Verstreken tijd: +164=Resterende tijd: +165=Huidige File: +166=Algemeen Voortgang: +167=Annuleren +168=OK +169=gebruikersnaam en wachtwoord +170=Gebruikersnaam: +171=Wachtwoord: diff --git a/DnsServiceSetup/Windows/depend/isxdl/english.ini b/DnsServiceSetup/Windows/depend/isxdl/english.ini new file mode 100644 index 00000000..55b2878b --- /dev/null +++ b/DnsServiceSetup/Windows/depend/isxdl/english.ini @@ -0,0 +1,49 @@ +[strings] +; General +100=File download +101=Do you want to cancel the download? +102=%1 (%2 of %3) +103=%1 KB +104=%1 KB of %2 KB (%3%) + +; Status information +110=Getting file information... +111=Redirecting to %1 +112=Sending request... +113=Resolving %1 +114=Connected to %1 +115=Receiving... +116=Connecting to %1 + +; Error messages +120=Error connecting to the internet.\n\n%1 +121=Error opening %1.\n\nThe server returned status code %2. +122=Error reading URL.\n\n%1 +123=Error writing file %1.\n\n%2 +124=Error opening file %1.\n\n%2 +125='%1' is an invalid URL. +126=Error opening %1.\n\n%2 +127=Error sending request.\n\n%1 +128=Unsupported protocol. Only HTTP and FTP protocols are supported. +129=Failed to connect to %1.\n\n%2 +130=Failed to query status code.\n\n%1 +131=Error requesting file.\n\n%1 + +; Other +144=About... +146=Download +147=Setup is now downloading additional files to your computer. + +; labels +160=File: +161=Speed: +162=Status: +163=Elapsed Time: +164=Remaining Time: +165=Current File: +166=Overall Progress: +167=Cancel +168=OK +169=User Name and Password +170=User Name: +171=Password: diff --git a/DnsServiceSetup/Windows/depend/isxdl/french.ini b/DnsServiceSetup/Windows/depend/isxdl/french.ini new file mode 100644 index 00000000..f5527bbc --- /dev/null +++ b/DnsServiceSetup/Windows/depend/isxdl/french.ini @@ -0,0 +1,45 @@ +[strings] +; General +100=Tlchargement des fichiers +101=Souhaitez-vous annuler le tlchargement ? +102=%1 (%2 / %3) +103=%1 Ko +104=%1 Ko / %2 Ko (%3%) + +; Etat du tlchargement +110=Accs au fichier... +111=Redirection vers %1 +112=Envoi de la requte... +113=Recherche %1 +114=Connect %1 +115=Rception... +116=Connexion %1 + +; Messages d'erreur +120=Impossible de se connecter internet.\n\n%1 +121=Impossible d'ouvrir %1.\n\nLe serveur a renvoy le code d'erreur %2. +122=Impossible de lire l'adresse.\n\n%1 +123=Impossible de crer le fichier %1.\n\n%2 +124=Impossible d'ouvrir le fichier %1.\n\n%2 +125='%1' est une adresse incorrecte. +126=Impossible d'ouvrir %1.\n\n%2 +127=Impossible d'accder au serveur.\n\n%1 +128=Protocole non support. Seuls les protocoles HTTP et FTP sont pris en charge. +129=Impossible de se connecter %1.\n\n%2 +130=Impossible de rcuprer le code d'tat.\n\n%1 +131=Impossible de rcuprer le fichier.\n\n%1 + +; Autre +144=A propos... +146=Tlchargement +147=Certains fichiers requis vont tre tlchargs. + +; Labels +160=Fichier : +161=Vitesse : +162=Etat : +163=Temps coul : +164=Temps restant : +165=Fichier courant : +166=Tous les fichiers : +167=Annuler diff --git a/DnsServiceSetup/Windows/depend/isxdl/french2.ini b/DnsServiceSetup/Windows/depend/isxdl/french2.ini new file mode 100644 index 00000000..a774639a --- /dev/null +++ b/DnsServiceSetup/Windows/depend/isxdl/french2.ini @@ -0,0 +1,45 @@ +[strings] +; General +100=Tlchargement de fichier +101=Voulez vous annuler le tlchargement ? +102=%1 (%2 de %3) +103=%1 Ko +104=%1 Ko de %2 Ko (%3%) + +; Status information +110=Rception des informations du fichier... +111=Redirection vers %1 +112=envoie de la demande... +113=Rsolution %1 +114=Connect a %1 +115=Rception... +116=Connexion %1 + +; Error messages +120=Erreur de connexion Internet.\n\n%1 +121=Erreur d'ouverture%1.\n\nLe Serveur rpondu par le code d'tat %2. +122=Erreur de lecture de l'URL.\n\n%1 +123=Erreur d'criture du fichier %1.\n\n%2 +124=Erreur d'ouverture du fichier %1.\n\n%2 +125='%1' est une URL invalide. +126=Erreur douverture %1.\n\n%2 +127=Erreur pendant l'envoi de la demande.\n\n%1 +128=Protocole non support. Seuls les protocoles HTTP et FTP sont accepts. +129=Echec de connexion %1.\n\n%2 +130=Echec d'obtention du code d'tat.\n\n%1 +131=Erreur lors de la demande du fichier.\n\n%1 + +; Other +144=A Propos... +146=Tlchargement +147=LiveUpdate tlcharge maintenant des fichiers complmentaires sur votre ordinateur. + +; labels +160=Fichier: +161=Vitesse: +162=Etat: +163=Temps coul: +164=Temps restant: +165=Fichier en cours: +166=Avancement global: +167=Annuler diff --git a/DnsServiceSetup/Windows/depend/isxdl/french3.ini b/DnsServiceSetup/Windows/depend/isxdl/french3.ini new file mode 100644 index 00000000..5d767709 --- /dev/null +++ b/DnsServiceSetup/Windows/depend/isxdl/french3.ini @@ -0,0 +1,46 @@ +; By Fabien ILLIDE (fabienillide@users.sourceforge.net) +[strings] +; General +100=Tlchargement de fichier +101=Voulez-vous annuler le tlchargement ? +102=%1 (%2 de %3) +103=%1 Ko +104=%1 Ko de %2 Ko (%3%) + +; Status information +110=Obtention des informations du fichier... +111=Redirection vers %1 +112=Envoi de la requte... +113=Rsolution de %1 +114=Connect %1 +115=Rception... +116=Connexion %1 + +; Error messages +120=Erreur de connexion Internet.\n\n%1 +121=Erreur en ouvrant %1.\n\nLe serveur retourn le code d'tat %2. +122=Erreur de lecture d'URL.\n\n%1 +123=Erreur d'criture pour %1.\n\n%2 +124=Erreur en ouvrant le fichier %1.\n\n%2 +125='%1' est une URL invalide. +126=Erreur en ouvrant %1.\n\n%2 +127=Erreur d'envoi de requte.\n\n%1 +128=Protocole non support. Seuls les protocoles HTTP et FTP sont supports. +129=Echec de connexion %1.\n\n%2 +130=Echec de demande du code d'tat.\n\n%1 +131=Erreur en demandant le fichier.\n\n%1 + +; Other +144=A propos... +146=Tlcharger +147=L'installateur tlcharge maintenant les fichiers additionnels sur votre ordinateur. + +; labels +160=Fichier : +161=Vitesse : +162=Etat : +163=Temps coul : +164=Temps restant : +165=Fichier en cours : +166=Avancement global : +167=Annuler diff --git a/DnsServiceSetup/Windows/depend/isxdl/german.ini b/DnsServiceSetup/Windows/depend/isxdl/german.ini new file mode 100644 index 00000000..b2204362 --- /dev/null +++ b/DnsServiceSetup/Windows/depend/isxdl/german.ini @@ -0,0 +1,49 @@ +[strings] +; General +100=Datei herunterladen +101=Mchten Sie das Herunterladen der Datei abbrechen? +102=%1 (%2 von %3) +103=%1 KB +104=%1 KB von %2 KB (%3%) + +; Status information +110=Dateiinformationen werden ermittelt... +111=Weiterleitung zu %1 +112=Anforderung wird gesendet... +113=Auflsen von %1 +114=Verbunden mit %1 +115=Empfange... +116=Verbinden mit %1 + +; Error messages +120=Fehler beim Verbinden mit dem Internet.\n\n%1 +121=Fehler beim ffnen von %1.\n\nDer Server meldet Statuscode %2. +122=Fehler beim Lesen der URL.\n\n%1 +123=Fehler beim Schreiben der Datei %1.\n\n%2 +124=Fehler beim ffnen der Datei %1.\n\n%2 +125='%1' ist eine ungltige URL. +126=Fehler beim ffnen von %1.\n\n%2 +127=Fehler beim Senden der Anforderung.\n\n%1 +128=Protokoll wird nicht untersttzt. Nur HTTP und FTP werden untersttzt. +129=Verbindung zu %1 fehlgeschlagen.\n\n%2 +130=Fehler bei der Abfrage des Statuscodes.\n\n%1 +131=Fehler bei der Anforderung der Datei.\n\n%1 + +; Other +144=ber... +146=Download +147=Das Setup ldt nun zustzliche Dateien auf Ihren Computer. + +; labels +160=Datei: +161=Geschwindigkeit: +162=Status: +163=Bisherige Zeit: +164=Verbleibende Zeit: +165=Aktuelle Datei: +166=Gesamter Vorgang: +167=Abbrechen +168=OK +169=Benutzername und Kennwort +170=Benutzername: +171=Kennwort: diff --git a/DnsServiceSetup/Windows/depend/isxdl/isxdl.dll b/DnsServiceSetup/Windows/depend/isxdl/isxdl.dll new file mode 100644 index 0000000000000000000000000000000000000000..d227bcad82b145e18a122348dab7227d3816bf90 GIT binary patch literal 124416 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 '')) then begin + Result := true; + end else begin + Result := false; + end; +end; + +function InstallProducts: InstallResult; +{ + Installs the downloaded products +} +var + resultCode, i, productCount, finishCount: Integer; +begin + Result := InstallSuccessful; + productCount := GetArrayLength(products); + + if productCount > 0 then begin + DependencyPage := CreateOutputProgressPage(CustomMessage('depinstall_title'), CustomMessage('depinstall_description')); + DependencyPage.Show; + + for i := 0 to productCount - 1 do begin + if (products[i].InstallClean and (delayedReboot or PendingReboot())) then begin + Result := InstallRebootRequired; + break; + end; + + DependencyPage.SetText(FmtMessage(CustomMessage('depinstall_status'), [products[i].Title]), ''); + DependencyPage.SetProgress(i, productCount); + + while true do begin + // set 0 as used code for shown error if SmartExec fails + resultCode := 0; + if SmartExec(products[i], resultCode) then begin + // setup executed; resultCode contains the exit code + if (products[i].MustRebootAfter) then begin + // delay reboot after install if we installed the last dependency anyways + if (i = productCount - 1) then begin + delayedReboot := true; + end else begin + Result := InstallRebootRequired; + end; + break; + end else if (resultCode = 0) or (products[i].ForceSuccess) then begin + finishCount := finishCount + 1; + break; + end else if (resultCode = 3010) then begin + // Windows Installer resultCode 3010: ERROR_SUCCESS_REBOOT_REQUIRED + delayedReboot := true; + finishCount := finishCount + 1; + break; + end; + end; + + case MsgBox(FmtMessage(SetupMessage(msgErrorFunctionFailed), [products[i].Title, IntToStr(resultCode)]), mbError, MB_ABORTRETRYIGNORE) of + IDABORT: begin + Result := InstallError; + break; + end; + IDIGNORE: begin + break; + end; + end; + end; + + if Result <> InstallSuccessful then begin + break; + end; + end; + + // only leave not installed products for error message + for i := 0 to productCount - finishCount - 1 do begin + products[i] := products[i+finishCount]; + end; + SetArrayLength(products, productCount - finishCount); + + DependencyPage.Hide; + end; +end; + +{ + -------------------- + INNO EVENT FUNCTIONS + -------------------- +} + +function PrepareToInstall(var NeedsRestart: boolean): String; +{ + Before the "preparing to install" page. + See: http://www.jrsoftware.org/ishelp/index.php?topic=scriptevents +} +var + i: Integer; + s: string; +begin + delayedReboot := false; + + case InstallProducts() of + InstallError: begin + s := CustomMessage('depinstall_error'); + + for i := 0 to GetArrayLength(products) - 1 do begin + s := s + #13 + ' ' + products[i].Title; + end; + + Result := s; + end; + InstallRebootRequired: begin + Result := products[0].Title; + NeedsRestart := true; + + // write into the registry that the installer needs to be executed again after restart + RegWriteStringValue(HKEY_CURRENT_USER, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', 'InstallBootstrap', ExpandConstant('{srcexe}')); + end; + end; +end; + +function NeedRestart : boolean; +{ + Checks whether a restart is needed at the end of install + See: http://www.jrsoftware.org/ishelp/index.php?topic=scriptevents +} +begin + Result := delayedReboot; +end; + +function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String; +{ + Just before the "ready" page. + See: http://www.jrsoftware.org/ishelp/index.php?topic=scriptevents +} +var + s: string; +begin + if downloadMessage <> '' then + s := s + CustomMessage('depdownload_memo_title') + ':' + NewLine + FmtMessage(downloadMessage, [Space]) + NewLine; + if installMemo <> '' then + s := s + CustomMessage('depinstall_memo_title') + ':' + NewLine + FmtMessage(installMemo, [Space]) + NewLine; + + if MemoDirInfo <> '' then + s := s + MemoDirInfo + NewLine + NewLine; + if MemoGroupInfo <> '' then + s := s + MemoGroupInfo + NewLine + NewLine; + if MemoTasksInfo <> '' then + s := s + MemoTasksInfo; + + Result := s +end; + +function NextButtonClick(CurPageID: Integer): boolean; +{ + At each "next" button click + See: http://www.jrsoftware.org/ishelp/index.php?topic=scriptevents +} +begin + Result := true; + + if CurPageID = wpReady then begin + if downloadMessage <> '' then begin + // change isxdl language only if it is not english because isxdl default language is already english + if (ActiveLanguage() <> 'en') then begin + ExtractTemporaryFile(CustomMessage('isxdl_langfile')); + isxdl_SetOption('language', ExpandConstant('{tmp}{\}') + CustomMessage('isxdl_langfile')); + end; + //isxdl_SetOption('title', FmtMessage(SetupMessage(msgSetupWindowTitle), [CustomMessage('appname')])); + + //if SuppressibleMsgBox(FmtMessage(CustomMessage('depdownload_msg'), [FmtMessage(downloadMessage, [''])]), mbConfirmation, MB_YESNO, IDYES) = IDNO then + // Result := false + //else if + if isxdl_DownloadFiles(StrToInt(ExpandConstant('{wizardhwnd}'))) = 0 then + Result := false; + end; + end; +end; + +{ + ----------------------------- + ARCHITECTURE HELPER FUNCTIONS + ----------------------------- +} + +function IsX86: boolean; +{ + Gets whether the computer is x86 (32 bits). +} +begin + Result := isForcedX86 or (ProcessorArchitecture = paX86) or (ProcessorArchitecture = paUnknown); +end; + +function IsX64: boolean; +{ + Gets whether the computer is x64 (64 bits). +} +begin + Result := (not isForcedX86) and Is64BitInstallMode and (ProcessorArchitecture = paX64); +end; + +function GetString(x86, x64: String; w64: boolean): String; +{ + Gets a string depending on the computer architecture. + Parameters: + x86: the string if the computer is x86 + x64: the string if the computer is x64 + ia64: the string if the computer is IA64 +} +begin + if (IsX64() and (x64 <> '')) or (w64 and IsWin64 and (x64 <> '')) then begin + Result := x64; + end else begin + Result := x86; + end; +end; + +function GetArchitectureString(w64: boolean): String; +{ + Gets the "standard" architecture suffix string. + Returns either _x64, _ia64 or nothing. +} +begin + if IsX64() or (w64 and IsWin64) then begin + Result := 'x64'; + end else begin + Result := 'x86'; + end; +end; + +procedure SetForceX86(value: boolean); +{ + Forces the setup to use X86 products +} +begin + isForcedX86 := value; +end; + +function StrSplit(Text: String; Separator: String): TArrayOfString; +var + i, p: Integer; + Dest: TArrayOfString; +begin + i := 0; + repeat + SetArrayLength(Dest, i+1); + p := Pos(Separator,Text); + if p > 0 then begin + Dest[i] := Copy(Text, 1, p-1); + Text := Copy(Text, p + Length(Separator), Length(Text)); + i := i + 1; + end else begin + Dest[i] := Text; + Text := ''; + end; + until Length(Text)=0; + Result := Dest +end; \ No newline at end of file diff --git a/DnsServiceSetup/Windows/depend/products/dotnet5.iss b/DnsServiceSetup/Windows/depend/products/dotnet5.iss new file mode 100644 index 00000000..1e739f83 --- /dev/null +++ b/DnsServiceSetup/Windows/depend/products/dotnet5.iss @@ -0,0 +1,35 @@ +[CustomMessages] +dotnet_501_desktop_title=.NET 5.0.1 32-Bit Desktop Runtime +dotnet_501_desktop_title_x64=.NET 5.0.1 64-Bit Desktop Runtime +dotnet_501_desktop_size=47.1 MB +dotnet_501_desktop_size_x64=52.5 MB +dotnet_501_desktop_url=http://download.visualstudio.microsoft.com/download/pr/55bb1094-db40-411d-8a37-21186e9495ef/1a045e29541b7516527728b973f0fdef/windowsdesktop-runtime-5.0.1-win-x86.exe +dotnet_501_desktop_url_x64=http://download.visualstudio.microsoft.com/download/pr/c6a74d6b-576c-4ab0-bf55-d46d45610730/f70d2252c9f452c2eb679b8041846466/windowsdesktop-runtime-5.0.1-win-x64.exe + +dotnet_501_runtime_title=.NET 5.0.1 32-Bit Runtime +dotnet_501_runtime_title_x64=.NET 5.0.1 64-Bit Runtime +dotnet_501_runtime_size=22.7 MB +dotnet_501_runtime_size_x64=25.3 MB +dotnet_501_runtime_url=http://download.visualstudio.microsoft.com/download/pr/f4fb5042-8134-4434-8835-499eb2f18b38/6a0d857f6f1833f5c54fbbe5ead028a7/dotnet-runtime-5.0.1-win-x86.exe +dotnet_501_runtime_url_x64=http://download.visualstudio.microsoft.com/download/pr/93095e51-be33-4b28-99c8-5ae0ebba753d/501f77f4b95d2e9c3481246a3eff9956/dotnet-runtime-5.0.1-win-x64.exe + + +dotnet_500_desktop_title=.NET 5.0.0 32-Bit Desktop Runtime +dotnet_500_desktop_title_x64=.NET 5.0.0 64-Bit Desktop Runtime +dotnet_500_desktop_size=47.1 MB +dotnet_500_desktop_size_x64=52.6 MB +dotnet_500_desktop_url=http://download.visualstudio.microsoft.com/download/pr/b2780d75-e54a-448a-95fc-da9721b2b4c2/62310a9e9f0ba7b18741944cbae9f592/windowsdesktop-runtime-5.0.0-win-x86.exe +dotnet_500_desktop_url_x64=http://download.visualstudio.microsoft.com/download/pr/1b3a8899-127a-4465-a3c2-7ce5e4feb07b/1e153ad470768baa40ed3f57e6e7a9d8/windowsdesktop-runtime-5.0.0-win-x64.exe + +dotnet_500_runtime_title=.NET 5.0.0 32-Bit Runtime +dotnet_500_runtime_title_x64=.NET 5.0.0 64-Bit Runtime +dotnet_500_runtime_size=22.7 MB +dotnet_500_runtime_size_x64=25.3 MB +dotnet_500_runtime_url=http://download.visualstudio.microsoft.com/download/pr/a7e15da3-7a15-43c2-a481-cf50bf305214/c69b951e8b47101e90b1289c387bb01a/dotnet-runtime-5.0.0-win-x86.exe +dotnet_500_runtime_url_x64=http://download.visualstudio.microsoft.com/download/pr/36a9dc4e-1745-4f17-8a9c-f547a12e3764/ae25e38f20a4854d5e015a88659a22f9/dotnet-runtime-5.0.0-win-x64.exe + + +[Code] +#include "dotnet5.pas" + +[Setup] diff --git a/DnsServiceSetup/Windows/depend/products/dotnet5.pas b/DnsServiceSetup/Windows/depend/products/dotnet5.pas new file mode 100644 index 00000000..f78414af --- /dev/null +++ b/DnsServiceSetup/Windows/depend/products/dotnet5.pas @@ -0,0 +1,160 @@ +const + NoInstallNet = '{param:skipnet|false}'; //if this parameter is supplied on the command line then skip installing .NET dependencies + +{ .NET 5.0.1 } + +function DotNet_501_Desktop_Installed: Boolean; +var + ResultCode: Integer; +begin + Result := false; + Exec('cmd.exe', '/c dotnet --list-runtimes | find /n "Microsoft.WindowsDesktop.App 5.0.1"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + if ResultCode = 0 then + begin + Result := true; + end; +end; + +function DotNet_501_Runtime_Installed: Boolean; +var + ResultCode: Integer; +begin + Result := false; + Exec('cmd.exe', '/c dotnet --list-runtimes | find /n "Microsoft.NETCore.App 5.0.1"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + if ResultCode = 0 then + begin + Result := true; + end; +end; + +procedure dotnet_501_desktop; +begin + if ExpandConstant(NoInstallNet) = 'false' then + begin + if not DotNet_501_Desktop_Installed() then + AddProduct('windowsdesktop-runtime-5.0.1-win' + GetArchitectureString(true) + '.exe', + '/install /quiet /norestart', + GetString(CustomMessage('dotnet_501_desktop_title'), CustomMessage('dotnet_501_desktop_title_x64'), true), + GetString(CustomMessage('dotnet_501_desktop_size'), CustomMessage('dotnet_501_desktop_size_x64'), true), + GetString(CustomMessage('dotnet_501_desktop_url'), CustomMessage('dotnet_501_desktop_url_x64'), true), + false, false, false); + end; +end; + +procedure dotnet_501_runtime; +begin + if ExpandConstant(NoInstallNet) = 'false' then + begin + if not DotNet_501_Runtime_Installed() then + AddProduct('dotnet-runtime-5.0.0-win' + GetArchitectureString(true) + '.exe', + '/install /quiet /norestart', + GetString(CustomMessage('dotnet_500_runtime_title'), CustomMessage('dotnet_500_runtime_title_x64'), true), + GetString(CustomMessage('dotnet_500_runtime_size'), CustomMessage('dotnet_500_runtime_size_x64'), true), + GetString(CustomMessage('dotnet_500_runtime_url'), CustomMessage('dotnet_500_runtime_url_x64'), true), + false, false, false); + end; +end; + +{ .NET 5.0.0 } + +function DotNet_500_Desktop_Installed: Boolean; +var + ResultCode: Integer; +begin + Result := false; + Exec('cmd.exe', '/c dotnet --list-runtimes | find /n "Microsoft.WindowsDesktop.App 5.0.0"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + if ResultCode = 0 then + begin + Result := true; + end; +end; + +function DotNet_500_Runtime_Installed: Boolean; +var + ResultCode: Integer; +begin + Result := false; + Exec('cmd.exe', '/c dotnet --list-runtimes | find /n "Microsoft.NETCore.App 5.0.0"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + if ResultCode = 0 then + begin + Result := true; + end; +end; + +procedure dotnet_500_desktop; +begin + if ExpandConstant(NoInstallNet) = 'false' then + begin + if not DotNet_500_Desktop_Installed() then + AddProduct('windowsdesktop-runtime-5.0.0-win' + GetArchitectureString(true) + '.exe', + '/install /quiet /norestart', + GetString(CustomMessage('dotnet_500_desktop_title'), CustomMessage('dotnet_500_desktop_title_x64'), true), + GetString(CustomMessage('dotnet_500_desktop_size'), CustomMessage('dotnet_500_desktop_size_x64'), true), + GetString(CustomMessage('dotnet_500_desktop_url'), CustomMessage('dotnet_500_desktop_url_x64'), true), + false, false, false); + end; +end; + +procedure dotnet_500_runtime; +begin + if ExpandConstant(NoInstallNet) = 'false' then + begin + if not DotNet_500_Runtime_Installed() then + AddProduct('dotnet-runtime-5.0.0-win' + GetArchitectureString(true) + '.exe', + '/install /quiet /norestart', + GetString(CustomMessage('dotnet_500_runtime_title'), CustomMessage('dotnet_500_runtime_title_x64'), true), + GetString(CustomMessage('dotnet_500_runtime_size'), CustomMessage('dotnet_500_runtime_size_x64'), true), + GetString(CustomMessage('dotnet_500_runtime_url'), CustomMessage('dotnet_500_runtime_url_x64'), true), + false, false, false); + end; +end; + +{ +any .NET 5 + +Checks for any version of .NET 5 and if none exists, installs latest +} + +function DotNet_5_Desktop_Installed: Boolean; +var + ResultCode: Integer; +begin + Result := false; + Exec('cmd.exe', '/c dotnet --list-runtimes | find /n "Microsoft.WindowsDesktop.App 5"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + // Only check for the 5 version number + if ResultCode = 0 then + begin + Result := true; + end; +end; + +function DotNet_5_Runtime_Installed: Boolean; +var + ResultCode: Integer; +begin + Result := false; + Exec('cmd.exe', '/c dotnet --list-runtimes | find /n "Microsoft.NETCore.App 5"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + // Only check for the 5 version number + if ResultCode = 0 then + begin + Result := true; + end; +end; + +procedure dotnet_5_desktop; +begin + if not DotNet_5_Desktop_Installed() then + begin + dotnet_501_desktop(); + { if no .NET 5 version installed then install the one above } + end; +end; + +procedure dotnet_5_runtime; +begin + if not DotNet_5_Runtime_Installed() then + begin + dotnet_501_runtime(); + { if no .NET 5 version installed then install the one above } + end; +end; \ No newline at end of file diff --git a/DnsServiceSetup/Windows/depend/products/dotnetfw4.iss b/DnsServiceSetup/Windows/depend/products/dotnetfw4.iss new file mode 100644 index 00000000..4b0a1e6d --- /dev/null +++ b/DnsServiceSetup/Windows/depend/products/dotnetfw4.iss @@ -0,0 +1,9 @@ +[CustomMessages] +dotnet_fw48_title=.NET Framework 4.8 Runtime +dotnet_fw48_size=111 MB +dotnet_fw48_url=http://download.visualstudio.microsoft.com/download/pr/014120d7-d689-4305-befd-3cb711108212/0fd66638cde16859462a6243a4629a50/ndp48-x86-x64-allos-enu.exe + +[Code] +#include "dotnetfw4.pas" + +[Setup] \ No newline at end of file diff --git a/DnsServiceSetup/Windows/depend/products/dotnetfw4.pas b/DnsServiceSetup/Windows/depend/products/dotnetfw4.pas new file mode 100644 index 00000000..abdc962a --- /dev/null +++ b/DnsServiceSetup/Windows/depend/products/dotnetfw4.pas @@ -0,0 +1,40 @@ +const + NoInstallNet = '{param:skipnet|false}'; //if this parameter is supplied on the command line then skip installing .NET dependencies + +{ .NET Framework 4.8 } + +function DotNet_Framework_48_Installed: Boolean; +var + KeyResult: String; + VersionParts: TArrayOfString; + RegVersion, MinVer: Int64; + VerDiff: Integer; +begin + Result := false; + + RegQueryStringValue(HKLM, 'Software\Microsoft\NET Framework Setup\NDP\v4\Full', 'Version', KeyResult); + VersionParts := StrSplit(KeyResult, '.'); + RegVersion := PackVersionComponents(StrToInt(VersionParts[0]), StrToInt(VersionParts[1]), StrToInt(VersionParts[2]), 0); + MinVer := PackVersionComponents(4, 8, 0, 0); + + VerDiff := ComparePackedVersion(RegVersion, MinVer); + + if VerDiff > -1 then + begin + Result := true; + end; +end; + +procedure dotnet_framework_48(); +begin + if ExpandConstant(NoInstallNet) = 'false' then + begin + if not DotNet_Framework_48_Installed() then + AddProduct('ndp48-x86-x64-allos-enu.exe', + '/install /quiet /norestart', + CustomMessage('dotnet_fw48_title'), + CustomMessage('dotnet_fw48_size'), + CustomMessage('dotnet_fw48_url'), + false, false, false); + end; +end; \ No newline at end of file diff --git a/DnsServiceSetup/Windows/service.pas b/DnsServiceSetup/Windows/service.pas new file mode 100644 index 00000000..754a2858 --- /dev/null +++ b/DnsServiceSetup/Windows/service.pas @@ -0,0 +1,224 @@ +type + SERVICE_STATUS = record + dwServiceType : cardinal; + dwCurrentState : cardinal; + dwControlsAccepted : cardinal; + dwWin32ExitCode : cardinal; + dwServiceSpecificExitCode : cardinal; + dwCheckPoint : cardinal; + dwWaitHint : cardinal; + end; + HANDLE = cardinal; + +const + SERVICE_QUERY_CONFIG = $1; + SERVICE_CHANGE_CONFIG = $2; + SERVICE_QUERY_STATUS = $4; + SERVICE_START = $10; + SERVICE_STOP = $20; + SERVICE_ALL_ACCESS = $f01ff; + SC_MANAGER_ALL_ACCESS = $f003f; + SERVICE_WIN32_OWN_PROCESS = $10; + SERVICE_WIN32_SHARE_PROCESS = $20; + SERVICE_WIN32 = $30; + SERVICE_INTERACTIVE_PROCESS = $100; + SERVICE_BOOT_START = $0; + SERVICE_SYSTEM_START = $1; + SERVICE_AUTO_START = $2; + SERVICE_DEMAND_START = $3; + SERVICE_DISABLED = $4; + SERVICE_DELETE = $10000; + SERVICE_CONTROL_STOP = $1; + SERVICE_CONTROL_PAUSE = $2; + SERVICE_CONTROL_CONTINUE = $3; + SERVICE_CONTROL_INTERROGATE = $4; + SERVICE_STOPPED = $1; + SERVICE_START_PENDING = $2; + SERVICE_STOP_PENDING = $3; + SERVICE_RUNNING = $4; + SERVICE_CONTINUE_PENDING = $5; + SERVICE_PAUSE_PENDING = $6; + SERVICE_PAUSED = $7; + + + ERROR_ACCESS_DENIED = 5; + ERROR_CIRCULAR_DEPENDENCY = 1059; + ERROR_DUPLICATE_SERVICE_NAME = 1078; + ERROR_INVALID_HANDLE = 6; + ERROR_INVALID_NAME = 123; + ERROR_INVALID_PARAMETER = 87; + ERROR_INVALID_SERVICE_ACCOUNT = 1057; + ERROR_SERVICE_EXISTS = 1073; + ERROR_SERVICE_MARKED_FOR_DELETE = 1072; + +// ####################################################################################### +// nt based service utilities +// ####################################################################################### +function OpenSCManager(lpMachineName, lpDatabaseName: string; dwDesiredAccess :cardinal): HANDLE; +external 'OpenSCManagerW@advapi32.dll stdcall'; + +function OpenService(hSCManager :HANDLE;lpServiceName: string; dwDesiredAccess :cardinal): HANDLE; +external 'OpenServiceW@advapi32.dll stdcall'; + +function CloseServiceHandle(hSCObject :HANDLE): boolean; +external 'CloseServiceHandle@advapi32.dll stdcall'; + +function CreateService(hSCManager :HANDLE;lpServiceName, lpDisplayName: string;dwDesiredAccess,dwServiceType,dwStartType,dwErrorControl: cardinal;lpBinaryPathName,lpLoadOrderGroup: String; lpdwTagId : cardinal;lpDependencies,lpServiceStartName,lpPassword :string): cardinal; +external 'CreateServiceW@advapi32.dll stdcall'; + +function DeleteService(hService :HANDLE): boolean; +external 'DeleteService@advapi32.dll stdcall'; + +function StartNTService(hService :HANDLE;dwNumServiceArgs : cardinal;lpServiceArgVectors : cardinal) : boolean; +external 'StartServiceW@advapi32.dll stdcall'; + +function ControlService(hService :HANDLE; dwControl :cardinal;var ServiceStatus :SERVICE_STATUS) : boolean; +external 'ControlService@advapi32.dll stdcall'; + +function QueryServiceStatus(hService :HANDLE;var ServiceStatus :SERVICE_STATUS) : boolean; +external 'QueryServiceStatus@advapi32.dll stdcall'; + +function QueryServiceStatusEx(hService :HANDLE;ServiceStatus :SERVICE_STATUS) : boolean; +external 'QueryServiceStatus@advapi32.dll stdcall'; + +function GetLastError(): dword; +external 'GetLastError@kernel32.dll stdcall'; + +function OpenServiceManager(): HANDLE; +begin + if UsingWinNT() = true then begin + Result := OpenSCManager('', 'ServicesActive', SC_MANAGER_ALL_ACCESS); + if Result = 0 then + MsgBox(ExpandConstant('{cm:ServiceManagerUnavailable}'), mbError, MB_OK); + end + else begin + MsgBox('only nt based systems support services', mbError, MB_OK); + Result := 0; + end +end; + +function IsServiceInstalled(ServiceName: string): boolean; +var + hSCM : HANDLE; + hService: HANDLE; +begin + hSCM := OpenServiceManager(); + Result := false; + if hSCM <> 0 then begin + hService := OpenService(hSCM, ServiceName, SERVICE_QUERY_CONFIG); + if hService <> 0 then begin + Result := true; + CloseServiceHandle(hService); + end; + CloseServiceHandle(hSCM); + end +end; + +function InstallService(FileName, ServiceName, DisplayName, Description: string; ServiceType, StartType: cardinal): boolean; +var + hSCM : HANDLE; + hService: HANDLE; +begin + hSCM := OpenServiceManager(); + Result := false; + if hSCM <> 0 then begin + hService := CreateService(hSCM, ServiceName, DisplayName, SERVICE_ALL_ACCESS, ServiceType, StartType, 0, FileName,'', 0, '', '', ''); + if hService <> 0 then begin + Result := true; + // Win2K & WinXP supports aditional description text for services + if Description <> '' then + RegWriteStringValue(HKLM,'System\CurrentControlSet\Services\' + ServiceName, 'Description', Description); + CloseServiceHandle(hService); + end; + CloseServiceHandle(hSCM); + end; +end; + +function RemoveService(ServiceName: string): boolean; +var + hSCM : HANDLE; + hService: HANDLE; +begin + hSCM := OpenServiceManager(); + Result := false; + if hSCM <> 0 then begin + hService := OpenService(hSCM, ServiceName, SERVICE_DELETE); + if hService <> 0 then begin + Result := DeleteService(hService); + CloseServiceHandle(hService); + end; + CloseServiceHandle(hSCM); + end; +end; + +function StartService(ServiceName: string): boolean; +var + hSCM : HANDLE; + hService: HANDLE; +begin + hSCM := OpenServiceManager(); + Result := false; + if hSCM <> 0 then begin + hService := OpenService(hSCM, ServiceName, SERVICE_START); + if hService <> 0 then begin + Result := StartNTService(hService, 0, 0); + CloseServiceHandle(hService); + end; + CloseServiceHandle(hSCM); + end; +end; + +function StopService(ServiceName: string): boolean; +var + hSCM : HANDLE; + hService: HANDLE; + Status : SERVICE_STATUS; +begin + hSCM := OpenServiceManager(); + Result := false; + if hSCM <> 0 then begin + hService := OpenService(hSCM, ServiceName, SERVICE_STOP); + if hService <> 0 then begin + Result := ControlService(hService, SERVICE_CONTROL_STOP, Status); + CloseServiceHandle(hService); + end; + CloseServiceHandle(hSCM); + end; +end; + +function IsServiceRunning(ServiceName: string): boolean; +var + hSCM : HANDLE; + hService: HANDLE; + Status : SERVICE_STATUS; +begin + hSCM := OpenServiceManager(); + Result := false; + if hSCM <> 0 then begin + hService := OpenService(hSCM, ServiceName, SERVICE_QUERY_STATUS); + if hService <> 0 then begin + if QueryServiceStatus(hService, Status) then begin + Result :=(Status.dwCurrentState = SERVICE_RUNNING); + end; + CloseServiceHandle(hService); + end; + CloseServiceHandle(hSCM); + end +end; + +function ServiceErrorToMessage(Error: word): string; +begin + case Error of + ERROR_ACCESS_DENIED: Result := 'Access Denied'; + ERROR_CIRCULAR_DEPENDENCY: Result := 'Circular Dependency'; + ERROR_DUPLICATE_SERVICE_NAME: Result := 'Duplicate Service Name'; + ERROR_INVALID_HANDLE: Result := 'Invalid Handle'; + ERROR_INVALID_NAME: Result := 'Invalid Name'; + ERROR_INVALID_PARAMETER: Result := 'Invalid Parameter'; + ERROR_INVALID_SERVICE_ACCOUNT: Result := 'Invalid Service Account'; + ERROR_SERVICE_EXISTS: Result := 'Service Exists'; + ERROR_SERVICE_MARKED_FOR_DELETE: Result := 'Service Marked For Deletion'; + else + Result := 'Unknown error: ' + IntToStr(Error); + end; +end; \ No newline at end of file From 2103d9a257b4f9cec7c88b49126011bd147ecb8c Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Wed, 6 Jan 2021 23:41:32 +1300 Subject: [PATCH 04/14] Create chocolatey package script and files. --- DnsServiceSetup/Chocolatey/VERIFICATION.txt | 1 + DnsServiceSetup/Chocolatey/build.ps1 | 55 +++++++++++++++++++ .../Chocolatey/chocolateyInstall.ps1.template | 14 +++++ .../Chocolatey/chocolateyUninstall.ps1 | 0 .../technitiumdnsserver.nuspec.template | 50 +++++++++++++++++ 5 files changed, 120 insertions(+) create mode 100644 DnsServiceSetup/Chocolatey/VERIFICATION.txt create mode 100644 DnsServiceSetup/Chocolatey/build.ps1 create mode 100644 DnsServiceSetup/Chocolatey/chocolateyInstall.ps1.template create mode 100644 DnsServiceSetup/Chocolatey/chocolateyUninstall.ps1 create mode 100644 DnsServiceSetup/Chocolatey/technitiumdnsserver.nuspec.template diff --git a/DnsServiceSetup/Chocolatey/VERIFICATION.txt b/DnsServiceSetup/Chocolatey/VERIFICATION.txt new file mode 100644 index 00000000..994aea82 --- /dev/null +++ b/DnsServiceSetup/Chocolatey/VERIFICATION.txt @@ -0,0 +1 @@ +I am the package maintainer AND the software developer for Technitium DNS Server. \ No newline at end of file diff --git a/DnsServiceSetup/Chocolatey/build.ps1 b/DnsServiceSetup/Chocolatey/build.ps1 new file mode 100644 index 00000000..c0586081 --- /dev/null +++ b/DnsServiceSetup/Chocolatey/build.ps1 @@ -0,0 +1,55 @@ +$buildfolder = "build/" +$installerfilename = "dnsserverinstall" +Write-Host "Chocolatey Package Builder" +Write-Host "--------------------------" +Write-Host "Building Inno Setup..." +iscc "/O." "/F$installerfilename" ..\Windows\DnsServiceSetup.iss + +$installerfilename = "${installerfilename}.exe" +$version = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("./${installerfilename}").ProductVersion +Write-Host "Version: $version" +Write-Host "Copying files..." +New-Item -ItemType Directory -Path "${buildfolder}" -ErrorAction Ignore | Out-Null +New-Item -ItemType Directory -Path "${buildfolder}/tools" -ErrorAction Ignore | Out-Null + +$nuspecfilename = "" + +$files = Get-ChildItem -Path . +foreach ($file in $files) { + if ($file.Name.StartsWith("build")) { + #Skip any build files starting with the word build (ie: build.ps1) + continue + } + + if ($file.Name.EndsWith(".template")) { + Write-Host "Build $($file.Name)" + $outfilename = ($file.Name -replace ".{9}$") + $templater = Get-Content "$($file.Name)" -Raw + + $templater = $templater -replace "%fileversion%", "$version" + $templater = $templater -replace "%installfile%", "$installerfilename" + + $outpath = "${buildfolder}/" + if (!$outfilename.EndsWith(".nuspec")) { + $outpath = "${outpath}/tools/" + } else { + $nuspecfilename = $outfilename + } + + $templater | Out-File "${outpath}/${outfilename}" + } else { + Write-Host "Copy $($file.Name)" + + $outpath = "${buildfolder}/" + if (!$file.Name.EndsWith(".nuspec")) { + $outpath = "${outpath}/tools/" + } else { + $nuspecfilename = $file.Name + } + + Copy-Item "$($file.Name)" "${outpath}/$($file.Name)" + } +} + +Write-Host "Create Package" +cpack ${buildfolder}/${nuspecfilename} --out ../Release \ No newline at end of file diff --git a/DnsServiceSetup/Chocolatey/chocolateyInstall.ps1.template b/DnsServiceSetup/Chocolatey/chocolateyInstall.ps1.template new file mode 100644 index 00000000..20b54b50 --- /dev/null +++ b/DnsServiceSetup/Chocolatey/chocolateyInstall.ps1.template @@ -0,0 +1,14 @@ +$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" +$file = "$toolsDir/%installfile%" + +$packageArgs = @{ + packageName = $env:ChocolateyPackageName + fileType = 'EXE' + + file = $file + + silentArgs = '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /skipnet=true' + validExitCodes= @(0) +} + +Install-ChocolateyInstallPackage @packageArgs \ No newline at end of file diff --git a/DnsServiceSetup/Chocolatey/chocolateyUninstall.ps1 b/DnsServiceSetup/Chocolatey/chocolateyUninstall.ps1 new file mode 100644 index 00000000..e69de29b diff --git a/DnsServiceSetup/Chocolatey/technitiumdnsserver.nuspec.template b/DnsServiceSetup/Chocolatey/technitiumdnsserver.nuspec.template new file mode 100644 index 00000000..84c2ac61 --- /dev/null +++ b/DnsServiceSetup/Chocolatey/technitiumdnsserver.nuspec.template @@ -0,0 +1,50 @@ + + + + technitiumdnsserver + %fileversion% + Shreyas Zare + Technitium DNS Server + Shreyas Zare, Contributors + https://technitium.com/dns/ + https://github.com/TechnitiumSoftware/DnsServer + https://github.com/TechnitiumSoftware/DnsServer/issues + Shreyas Zare + dns dhcp + https://technitium.com/img/logo25x25.png + + + + Open Source DNS and DHCP Server. + +Technitium DNS Server is an open source tool that can be used for self hosting a local DNS server for privacy & security or, used for experimentation/testing by software developers on their computer. It works out-of-the-box with no or minimal configuration and provides a user friendly web console accessible using any web browser. + +Features: +* Works on Windows, Linux, macOS and Raspberry Pi. +* Installs in just a minute and works out-of-the-box with zero configuration. +* Block Ads using one or more block list URLs. +* Run [DNS-over-TLS](https://en.wikipedia.org/wiki/DNS_over_TLS) and [DNS-over-HTTPS](https://en.wikipedia.org/wiki/DNS_over_HTTPS) DNS service on your network. +* Use public DNS resolvers like Cloudflare, Google & Quad9 with DNS-over-TLS and DNS-over-HTTPS protocols as forwarders. +* Advance caching with features like serve stale, prefetching and auto prefetching. +* Supports working as an authoritative as well as a recursive DNS server. +* CNAME cloaking feature to block domain names that resolve to CNAME which are blocked. +* QNAME minimization support in recursive resolver [draft-ietf-dnsop-rfc7816bis-04](https://tools.ietf.org/html/draft-ietf-dnsop-rfc7816bis-04). +* QNAME randomization support for UDP transport protocol [draft-vixie-dnsext-dns0x20-00](https://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00). +* ANAME propriety record support to allow using CNAME like feature at zone root. +* Primary, Secondary, Stub and Conditional Forwarder zone support. +* Host domain names on your own DNS server. +* Wildcard sub domain support. +* Enable/disable zones and records to allow testing with ease. +* Built-in DNS Client with option to import responses to local zone. +* Supports out-of-order DNS request processing for DNS-over-TCP and DNS-over-TLS protocols. +* Built-in DHCP Server that can work for multiple networks. +* IPv6 support in DNS server core. +* HTTP & SOCKS5 proxy support which can be configured to route DNS over Tor Network or use Cloudflare's hidden DNS resolver. +* Web console portal for easy configuration using any web browser. +* Built-in system logging and query logging. + + + + + + \ No newline at end of file From 64e76ff0408f6f7d167b955bc817d545f9a31d43 Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Thu, 7 Jan 2021 02:39:31 +1300 Subject: [PATCH 05/14] Additional improvements to installer Fix installer to properly kill tray app. Add icon and image to installer. Add some sleep when stopping and removing service. --- DnsServiceSetup/Windows/DnsServiceSetup.iss | 6 +++++- DnsServiceSetup/Windows/DnsServiceSetup.pas | 18 ++++++++++++++++-- DnsServiceSetup/Windows/logo.bmp | Bin 0 -> 3382 bytes DnsServiceSetup/Windows/logo.ico | Bin 0 -> 8478 bytes 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 DnsServiceSetup/Windows/logo.bmp create mode 100644 DnsServiceSetup/Windows/logo.ico diff --git a/DnsServiceSetup/Windows/DnsServiceSetup.iss b/DnsServiceSetup/Windows/DnsServiceSetup.iss index 38bd1ea6..349828da 100644 --- a/DnsServiceSetup/Windows/DnsServiceSetup.iss +++ b/DnsServiceSetup/Windows/DnsServiceSetup.iss @@ -5,6 +5,7 @@ #define TITLE "Technitium DNS Server" #define FILES_LOCATION "..\..\DnsService\bin\Release" #define TRAYAPP_LOCATION "..\..\DnsServerSystemTrayApp\obj\Release" +#define TRAYAPP_FILENAME "DnsServerSystemTrayApp.exe" [Setup] PrivilegesRequired=admin @@ -18,10 +19,13 @@ AppCopyright=Copyright (c) 2021 {#COMPANY} AppPublisher={#COMPANY} OutputDir=..\Release OutputBaseFilename=DnsServiceSetup +CloseApplications=no Compression=lzma2/max +SetupIconFile=logo.ico +WizardSmallImageFile=logo.bmp [Files] -Source: "{#TRAYAPP_LOCATION}\DnsServerSystemTrayApp.exe"; DestDir: "{app}"; +Source: "{#TRAYAPP_LOCATION}\{#TRAYAPP_FILENAME}"; DestDir: "{app}"; BeforeInstall: KillTrayApp; Source: "{#FILES_LOCATION}\*.*"; Excludes: "*.pdb,DnsService.exe"; DestDir: "{app}"; Flags: recursesubdirs; Source: "{#FILES_LOCATION}\DnsService.exe"; DestDir: "{app}"; Flags: recursesubdirs; BeforeInstall: DoRemoveService; AfterInstall: DoInstallService; diff --git a/DnsServiceSetup/Windows/DnsServiceSetup.pas b/DnsServiceSetup/Windows/DnsServiceSetup.pas index 33c25110..da6c916b 100644 --- a/DnsServiceSetup/Windows/DnsServiceSetup.pas +++ b/DnsServiceSetup/Windows/DnsServiceSetup.pas @@ -25,6 +25,18 @@ begin Result := true; end; +procedure TaskKill(fileName: String); +var + ResultCode: Integer; +begin + Exec(ExpandConstant('taskkill.exe'), '/f /im ' + '"' + fileName + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); +end; + +procedure KillTrayApp; //Kill the tray app. Inno Setup cannot seem to close it through the "Close Applications" dialog. +begin + TaskKill('{#TRAYAPP_FILENAME}'); +end; + procedure DoRemoveService(); //Removes the dns service from the scm begin if IsServiceInstalled(ExpandConstant('{cm:ServiceName}')) then begin @@ -32,11 +44,12 @@ begin if IsServiceRunning(ExpandConstant('{cm:ServiceName}')) then begin Log('Service: Already running'); StopService(ExpandConstant('{cm:ServiceName}')); - Sleep(5000); + Sleep(3000); end; Log('Service: Remove'); - RemoveService(ExpandConstant('{cm:ServiceName}')) + RemoveService(ExpandConstant('{cm:ServiceName}')); + Sleep(3000); end; end; @@ -61,6 +74,7 @@ procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); begin if CurUninstallStep = usUninstall then begin + KillTrayApp(); DoRemoveService(); end; end; \ No newline at end of file diff --git a/DnsServiceSetup/Windows/logo.bmp b/DnsServiceSetup/Windows/logo.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8659111eda4a78fa4fd5b2df6779174de13f68c1 GIT binary patch literal 3382 zcmeH`p-&u95XP6}#0e@eC`?hsHJOA2d!7V?#?8NgsaS<>(uCwWMBAjIhQHucMHyQ# zMIlH?lNLu(@%1;m&D*={1<@2y+TGjTneWYe-*4ybO?J1RG~Ka1YMN(6&wpMk&A1^RNp?n)o3)*cs$nX>Z;b(*0jF9u8oZiZEkLA zYimpU`}^A6-PL3=(az3}+85H^p7g3Ym%fBPoPX8%^|fBUdyk)SIalEdHHNpL8JeM4 zYEt9qj_&A=?&ywQ&?ie|XItzdxXV3{87Kg=QaX1_fhr{7;I2e+{;cz$%4g;lK7m~nXa2P1brm3(- z!Kc7bGKZ_+U*IZuGpvGl!MngzFeogDEpyTEE_fHbd8n{(>AcbLFZeUm?LfYLp*N_U z=paP;(TBh^2E+^uF1|=v||-DI|R(@}+Z8t`44O z2gMVe#n_pgm__Vo?opl`eUgEmHg9xraG=A(LmeF*>G=3qXJ=&&o^Ik^smoA4Q8STt{*H1qpHWB(L&It^Y@&-P*#=s%3Af;*Dq;Vm4;w=->NN< f>e$egd!=QA%lf>aF>Nqk7+aP;s`{a44-x$U;z-r) literal 0 HcmV?d00001 diff --git a/DnsServiceSetup/Windows/logo.ico b/DnsServiceSetup/Windows/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..67432fd83dad417f419e15c0af9f4209634b0a42 GIT binary patch literal 8478 zcmeI1zfUAL6o4Peo`ezF4Z?dUc3nMy&OyU+sp6A|H$#{*Ye#jzY@q$E!9zd5)C?o z&Y&}@<3>^wi{3J@=q-ASUY9xa4!uKg=pA}G@#sBzkKUv3dP5=y48UurY!N6c(h)6IP7z_*s1_MLHgu%dIU@$Nk7$Q0h1_lFzfx*CF0y;Ph97Z|V zBcO^Dme#^zVX?5pLx#l?+!Dy*)WQF4W zorS}~U}5la$MPwK#7Te1F}<1{sEF;bi>W5IgUJ!p5x~LZi0znk#CF7X#Ez#U#zs$k zM|?+oJw=ZAj@&pH91KqPN{tuKgl51|Y4tRc15Z2;n}@>_FP>WEAO{`}uN)j$Jl!nv zuy|NJ5T1bX5`e|C&|}0MOGU4ji?VP|JS<-G5n1$V(6fLJh5$o=A;1t|2r$IU2?h^C zfFZ!(VZaR)dl&)?0fqnruBqr@2r#g_QT4L|A2N(;(!V#ixwXyQK{3zSo z+p@E>BfGo1vbVP<2L}hTzrQahCns`xdMamUXL5diE*BRUa(Q_vZ~hJP!^@Z4#}(Pt zdq6wiaL8vnLVfQ zEHQgdd7G{9wrkQ>&Z=flpV}wSkJi4M*qn-Ts#iIYy1UzVPZBZAL%Z{qeJ&&{=0 z^OU~NKj&Rsf2pSCY4+Y>_I;jjvi!EsyMftzhll^(fggE`CklP?=*a2gSAPwrG{(uW tPfq6TypH0+IEeoH)U091Yv1e?Ppr_CNU_({ca+ literal 0 HcmV?d00001 From 15c50c3008d3b70ca8f59dcd8c28fbcabd35611b Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Thu, 7 Jan 2021 02:41:05 +1300 Subject: [PATCH 06/14] Improvements to Chocolatey Package Fixed a few bugs. Improved build script reliability. Added logging for install. Improved uninstall process. --- DnsServiceSetup/Chocolatey/.skipAutoUninstall | 0 DnsServiceSetup/Chocolatey/build.ps1 | 37 +++++++++++++------ .../Chocolatey/chocolateyInstall.ps1.template | 2 +- .../Chocolatey/chocolateyUninstall.ps1 | 31 ++++++++++++++++ .../technitiumdnsserver.nuspec.template | 2 +- 5 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 DnsServiceSetup/Chocolatey/.skipAutoUninstall diff --git a/DnsServiceSetup/Chocolatey/.skipAutoUninstall b/DnsServiceSetup/Chocolatey/.skipAutoUninstall new file mode 100644 index 00000000..e69de29b diff --git a/DnsServiceSetup/Chocolatey/build.ps1 b/DnsServiceSetup/Chocolatey/build.ps1 index c0586081..e35373d2 100644 --- a/DnsServiceSetup/Chocolatey/build.ps1 +++ b/DnsServiceSetup/Chocolatey/build.ps1 @@ -1,17 +1,29 @@ $buildfolder = "build/" $installerfilename = "dnsserverinstall" -Write-Host "Chocolatey Package Builder" -Write-Host "--------------------------" -Write-Host "Building Inno Setup..." -iscc "/O." "/F$installerfilename" ..\Windows\DnsServiceSetup.iss - -$installerfilename = "${installerfilename}.exe" -$version = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("./${installerfilename}").ProductVersion -Write-Host "Version: $version" -Write-Host "Copying files..." +Write-Host "DNS Server Chocolatey Package Builder" +Write-Host "-------------------------------------" +Write-Host "Create build folders..." New-Item -ItemType Directory -Path "${buildfolder}" -ErrorAction Ignore | Out-Null New-Item -ItemType Directory -Path "${buildfolder}/tools" -ErrorAction Ignore | Out-Null +Write-Host "Building Setup..." +iscc "/O./${buildfolder}/tools" "/F$installerfilename" ..\Windows\DnsServiceSetup.iss | Out-Null +if ($LASTEXITCODE -ne 0) { + Write-Host "Error: Inno Setup Compile Failed!" + return +} else { + Write-Host "Build Success!" +} + +$installerfilename = "${installerfilename}.exe" +$version = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("./${buildfolder}/tools/${installerfilename}").ProductVersion +Write-Host "Version: $version" +if (!$version) { + Write-Host "Error: Could not get Product Version from Installer File" + return +} +Write-Host "Copying files..." + $nuspecfilename = "" $files = Get-ChildItem -Path . @@ -51,5 +63,8 @@ foreach ($file in $files) { } } -Write-Host "Create Package" -cpack ${buildfolder}/${nuspecfilename} --out ../Release \ No newline at end of file +Write-Host "Create Package..." +cpack ${buildfolder}/${nuspecfilename} --out ../Release +Write-Host "Remove Build Folder" +Remove-Item -Path "${buildfolder}" -Recurse +Write-Host "COMPLETE!" \ No newline at end of file diff --git a/DnsServiceSetup/Chocolatey/chocolateyInstall.ps1.template b/DnsServiceSetup/Chocolatey/chocolateyInstall.ps1.template index 20b54b50..d80bdbb3 100644 --- a/DnsServiceSetup/Chocolatey/chocolateyInstall.ps1.template +++ b/DnsServiceSetup/Chocolatey/chocolateyInstall.ps1.template @@ -7,7 +7,7 @@ $packageArgs = @{ file = $file - silentArgs = '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /skipnet=true' + silentArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /LOG=`"$($env:TEMP)\$($env:chocolateyPackageName).$($env:chocolateyPackageVersion).Install.log`" /skipnet=true" validExitCodes= @(0) } diff --git a/DnsServiceSetup/Chocolatey/chocolateyUninstall.ps1 b/DnsServiceSetup/Chocolatey/chocolateyUninstall.ps1 index e69de29b..959fd313 100644 --- a/DnsServiceSetup/Chocolatey/chocolateyUninstall.ps1 +++ b/DnsServiceSetup/Chocolatey/chocolateyUninstall.ps1 @@ -0,0 +1,31 @@ +$ErrorActionPreference = 'Stop'; + +$packageName = 'technitiumdnsserver' +$softwareName = 'Technitium DNS Server*' +$installerType = 'EXE' + +$silentArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /LOG=`"$($env:TEMP)\$($env:chocolateyPackageName).$($env:chocolateyPackageVersion).Uninstall.log`"" +$validExitCodes = @(0) + +$uninstalled = $false +[array]$key = Get-UninstallRegistryKey -SoftwareName $softwareName + +if ($key.Count -eq 1) { + $key | ForEach-Object { + $file = "$($_.UninstallString.Trim('"'))" + + Uninstall-ChocolateyPackage ` + -PackageName $packageName ` + -FileType $installerType ` + -SilentArgs "$silentArgs" ` + -ValidExitCodes $validExitCodes ` + -File "$file" + } +} elseif ($key.Count -eq 0) { + Write-Warning "$packageName has already been uninstalled by other means." +} elseif ($key.Count -gt 1) { + Write-Warning "$key.Count matches found!" + Write-Warning "To prevent accidental data loss, no programs will be uninstalled." + Write-Warning "Please alert package maintainer the following keys were matched:" + $key | ForEach-Object {Write-Warning "- $_.DisplayName"} +} \ No newline at end of file diff --git a/DnsServiceSetup/Chocolatey/technitiumdnsserver.nuspec.template b/DnsServiceSetup/Chocolatey/technitiumdnsserver.nuspec.template index 84c2ac61..12791a10 100644 --- a/DnsServiceSetup/Chocolatey/technitiumdnsserver.nuspec.template +++ b/DnsServiceSetup/Chocolatey/technitiumdnsserver.nuspec.template @@ -45,6 +45,6 @@ Features: - + \ No newline at end of file From a6bfa289a887a4fd2934c93b797519fb39c1d5fe Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Thu, 7 Jan 2021 23:19:29 +1300 Subject: [PATCH 07/14] Further improvements to installer Add prompt in uninstaller to remove configuration files as well as uninstall parameter. Add additional comments. Some additional variable definitions. --- DnsServiceSetup/Windows/DnsServiceSetup.iss | 12 +++++- DnsServiceSetup/Windows/DnsServiceSetup.pas | 43 +++++++++++++++++---- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/DnsServiceSetup/Windows/DnsServiceSetup.iss b/DnsServiceSetup/Windows/DnsServiceSetup.iss index 349828da..0734f642 100644 --- a/DnsServiceSetup/Windows/DnsServiceSetup.iss +++ b/DnsServiceSetup/Windows/DnsServiceSetup.iss @@ -3,10 +3,16 @@ #define PRODUCT_VERSION "5.6" #define COMPANY "Technitium" #define TITLE "Technitium DNS Server" + #define FILES_LOCATION "..\..\DnsService\bin\Release" #define TRAYAPP_LOCATION "..\..\DnsServerSystemTrayApp\obj\Release" #define TRAYAPP_FILENAME "DnsServerSystemTrayApp.exe" +#define SERVICE_NAME "DnsService" +#define SERVICE_DISPLAY_NAME "Technitium DNS Server" +#define SERVICE_DESCRIPTION "Technitium DNS Server" +#define CONFIG_FOLDER "{app}\config" + [Setup] PrivilegesRequired=admin AppName={#TITLE} @@ -33,8 +39,8 @@ Source: "{#FILES_LOCATION}\DnsService.exe"; DestDir: "{app}"; Flags: recursesubd Name: "desktopicon"; Description: "Create an icon on the &desktop"; [CustomMessages] -ServiceName=DnsService -ServiceDisplayName=Technitium DNS Server +RemoveConfig=Do you want to remove the configuration files?%n%nClick No to keep your settings. +RemoveConfigFail=Some configuration files could not be automatically removed. ServiceInstallFailure=The DNS Service could not be installed. %1 ServiceManagerUnavailable=The Service Manager is not available! DependenciesDir=. @@ -47,9 +53,11 @@ Root: HKCU; Subkey: "Software\{#COMPANY}"; Flags: uninsdeletekeyifempty Name: "{userprograms}\Technitium DNS Server"; Comment: "DNS Server Tray App"; Filename: "{app}\DnsServerSystemTrayApp.exe"; WorkingDir: "{app}\"; Flags: createonlyiffileexists Name: "{userdesktop}\Technitium DNS Server"; Filename: "{app}\DnsServerSystemTrayApp.exe"; WorkingDir: "{app}\"; Flags: createonlyiffileexists; Tasks: desktopicon +;Include the dependency code #include "depend\lang\english.iss" #include "depend\products.iss" #include "depend\products\dotnet5.iss" [Code] +//Include the setup code #include "DnsServiceSetup.pas" \ No newline at end of file diff --git a/DnsServiceSetup/Windows/DnsServiceSetup.pas b/DnsServiceSetup/Windows/DnsServiceSetup.pas index da6c916b..a77514f4 100644 --- a/DnsServiceSetup/Windows/DnsServiceSetup.pas +++ b/DnsServiceSetup/Windows/DnsServiceSetup.pas @@ -1,5 +1,5 @@ -#include "service.pas" //Include the sc functionality +#include "service.pas" function IsUpgrade: Boolean; //Check to see if the install is an upgrade var @@ -25,7 +25,7 @@ begin Result := true; end; -procedure TaskKill(fileName: String); +procedure TaskKill(fileName: String); //Kills an app by its filename var ResultCode: Integer; begin @@ -39,16 +39,16 @@ end; procedure DoRemoveService(); //Removes the dns service from the scm begin - if IsServiceInstalled(ExpandConstant('{cm:ServiceName}')) then begin + if IsServiceInstalled('{#SERVICE_NAME}') then begin Log('Service: Already installed'); - if IsServiceRunning(ExpandConstant('{cm:ServiceName}')) then begin + if IsServiceRunning('{#SERVICE_NAME}') then begin Log('Service: Already running'); - StopService(ExpandConstant('{cm:ServiceName}')); + StopService('{#SERVICE_NAME}'); Sleep(3000); end; Log('Service: Remove'); - RemoveService(ExpandConstant('{cm:ServiceName}')); + RemoveService('{#SERVICE_NAME}'); Sleep(3000); end; end; @@ -59,14 +59,40 @@ var MsgResult: Integer; begin Log('Service: Begin Install'); - InstallSuccess := InstallService(ExpandConstant('{app}\DnsService.exe'), ExpandConstant('{cm:ServiceName}'), ExpandConstant('{cm:ServiceDisplayName}'), ExpandConstant('{cm:ServiceDisplayName}'), SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START); + InstallSuccess := InstallService(ExpandConstant('{app}\DnsService.exe'), '{#SERVICE_NAME}', '{#SERVICE_DISPLAY_NAME}', '{#SERVICE_DESCRIPTION}', SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START); if not InstallSuccess then begin Log('Service: Install Fail ' + ServiceErrorToMessage(GetLastError())); SuppressibleMsgBox(ExpandConstant('{cm:ServiceInstallFailure,' + ServiceErrorToMessage(GetLastError()) + '}'), mbCriticalError, MB_OK, IDOK); end else begin Log('Service: Install Success, Starting'); - StartService(ExpandConstant('{cm:ServiceName}')); + StartService('{#SERVICE_NAME}'); + end; +end; + +procedure RemoveConfiguration(); //Removes the configuration left by the DNS Server +var + DeleteSuccess: Boolean; +begin + Log('Delete configuration folder'); + DeleteSuccess := DelTree(ExpandConstant('{#CONFIG_FOLDER}'), True, True, True); + if not DeleteSuccess then + begin + Log('Not all configuration files were deleted succesfully in ' + ExpandConstant('{#CONFIG_FOLDER}')); + SuppressibleMsgBox(ExpandConstant('{cm:RemoveConfigFail}'), mbError, MB_OK, IDOK); + end; +end; + +procedure PromptRemoveConfiguration(); //Asks users if they want their config removed. On unattended installs, will keep config unless /removeconfig=true is supplied +begin + case ExpandConstant('{param:removeconfig|prompt}') of + 'prompt': + if SuppressibleMsgBox(ExpandConstant('{cm:RemoveConfig}'), mbConfirmation, MB_YESNO or MB_DEFBUTTON2, IDNO) = IDYES then + begin + RemoveConfiguration(); + end; + 'true': + RemoveConfiguration(); end; end; @@ -76,5 +102,6 @@ begin begin KillTrayApp(); DoRemoveService(); + PromptRemoveConfiguration(); end; end; \ No newline at end of file From cf96a0520959cdcd74564641657d619121725d63 Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Thu, 7 Jan 2021 23:27:00 +1300 Subject: [PATCH 08/14] Add script to build inno setup. --- DnsServiceSetup/Windows/build.ps1 | 1 + 1 file changed, 1 insertion(+) create mode 100644 DnsServiceSetup/Windows/build.ps1 diff --git a/DnsServiceSetup/Windows/build.ps1 b/DnsServiceSetup/Windows/build.ps1 new file mode 100644 index 00000000..4282c706 --- /dev/null +++ b/DnsServiceSetup/Windows/build.ps1 @@ -0,0 +1 @@ +iscc DnsServiceSetup.iss \ No newline at end of file From 36535cf194919c95b0e6cb75f82c1d4f288c3af8 Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Mon, 25 Jan 2021 19:50:49 +1300 Subject: [PATCH 09/14] Update NET Dependency Update installer to install .NET 5.0.2 dependency. Fix .NET 5.0.1 desktop bug. --- .../Windows/depend/products/dotnet5.iss | 16 ++++- .../Windows/depend/products/dotnet5.pas | 65 +++++++++++++++++-- 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/DnsServiceSetup/Windows/depend/products/dotnet5.iss b/DnsServiceSetup/Windows/depend/products/dotnet5.iss index 1e739f83..0ea36f79 100644 --- a/DnsServiceSetup/Windows/depend/products/dotnet5.iss +++ b/DnsServiceSetup/Windows/depend/products/dotnet5.iss @@ -1,4 +1,19 @@ [CustomMessages] +dotnet_502_desktop_title=.NET 5.0.2 32-Bit Desktop Runtime +dotnet_502_desktop_title_x64=.NET 5.0.2 64-Bit Desktop Runtime +dotnet_502_desktop_size=47.1 MB +dotnet_502_desktop_size_x64=52.5 MB +dotnet_502_desktop_url=https://download.visualstudio.microsoft.com/download/pr/adeb8933-7480-4015-abf6-ca31137ad7cd/1123096ebfa5ee3f36d77500b622e4d8/windowsdesktop-runtime-5.0.2-win-x86.exe +dotnet_502_desktop_url_x64=https://download.visualstudio.microsoft.com/download/pr/deffc9d5-ef77-4697-ac6e-33a58ccdc409/8386e478b5823a765dc1361155360877/windowsdesktop-runtime-5.0.2-win-x64.exe + +dotnet_502_runtime_title=.NET 5.0.2 32-Bit Runtime +dotnet_502_runtime_title_x64=.NET 5.0.2 64-Bit Runtime +dotnet_502_runtime_size=22.8 MB +dotnet_502_runtime_size_x64=25.3 MB +dotnet_502_runtime_url=https://download.visualstudio.microsoft.com/download/pr/46f8a025-ea67-4288-8e6a-709cfebc9b4b/69045dd85bcbfdd50441a87dedc13bd0/dotnet-runtime-5.0.2-win-x86.exe +dotnet_502_runtime_url_x64=https://download.visualstudio.microsoft.com/download/pr/8526b5e6-e6e7-4d0b-902b-1f4ad5dd1462/7de15b8c6ab0387402d5958e99a62ed9/dotnet-runtime-5.0.2-win-x64.exe + + dotnet_501_desktop_title=.NET 5.0.1 32-Bit Desktop Runtime dotnet_501_desktop_title_x64=.NET 5.0.1 64-Bit Desktop Runtime dotnet_501_desktop_size=47.1 MB @@ -28,7 +43,6 @@ dotnet_500_runtime_size_x64=25.3 MB dotnet_500_runtime_url=http://download.visualstudio.microsoft.com/download/pr/a7e15da3-7a15-43c2-a481-cf50bf305214/c69b951e8b47101e90b1289c387bb01a/dotnet-runtime-5.0.0-win-x86.exe dotnet_500_runtime_url_x64=http://download.visualstudio.microsoft.com/download/pr/36a9dc4e-1745-4f17-8a9c-f547a12e3764/ae25e38f20a4854d5e015a88659a22f9/dotnet-runtime-5.0.0-win-x64.exe - [Code] #include "dotnet5.pas" diff --git a/DnsServiceSetup/Windows/depend/products/dotnet5.pas b/DnsServiceSetup/Windows/depend/products/dotnet5.pas index f78414af..d9705d50 100644 --- a/DnsServiceSetup/Windows/depend/products/dotnet5.pas +++ b/DnsServiceSetup/Windows/depend/products/dotnet5.pas @@ -1,6 +1,59 @@ const NoInstallNet = '{param:skipnet|false}'; //if this parameter is supplied on the command line then skip installing .NET dependencies +{ .NET 5.0.2 } +function DotNet_502_Desktop_Installed: Boolean; +var + ResultCode: Integer; +begin + Result := false; + Exec('cmd.exe', '/c dotnet --list-runtimes | find /n "Microsoft.WindowsDesktop.App 5.0.2"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + if ResultCode = 0 then + begin + Result := true; + end; +end; + +function DotNet_502_Runtime_Installed: Boolean; +var + ResultCode: Integer; +begin + Result := false; + Exec('cmd.exe', '/c dotnet --list-runtimes | find /n "Microsoft.NETCore.App 5.0.2"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + if ResultCode = 0 then + begin + Result := true; + end; +end; + +procedure dotnet_502_desktop; +begin + if ExpandConstant(NoInstallNet) = 'false' then + begin + if not DotNet_502_Desktop_Installed() then + AddProduct('windowsdesktop-runtime-5.0.2-win' + GetArchitectureString(true) + '.exe', + '/install /quiet /norestart', + GetString(CustomMessage('dotnet_502_desktop_title'), CustomMessage('dotnet_502_desktop_title_x64'), true), + GetString(CustomMessage('dotnet_502_desktop_size'), CustomMessage('dotnet_502_desktop_size_x64'), true), + GetString(CustomMessage('dotnet_502_desktop_url'), CustomMessage('dotnet_502_desktop_url_x64'), true), + false, false, false); + end; +end; + +procedure dotnet_502_runtime; +begin + if ExpandConstant(NoInstallNet) = 'false' then + begin + if not DotNet_502_Runtime_Installed() then + AddProduct('dotnet-runtime-5.0.2-win' + GetArchitectureString(true) + '.exe', + '/install /quiet /norestart', + GetString(CustomMessage('dotnet_502_runtime_title'), CustomMessage('dotnet_502_runtime_title_x64'), true), + GetString(CustomMessage('dotnet_502_runtime_size'), CustomMessage('dotnet_502_runtime_size_x64'), true), + GetString(CustomMessage('dotnet_502_runtime_url'), CustomMessage('dotnet_502_runtime_url_x64'), true), + false, false, false); + end; +end; + { .NET 5.0.1 } function DotNet_501_Desktop_Installed: Boolean; @@ -46,11 +99,11 @@ begin if ExpandConstant(NoInstallNet) = 'false' then begin if not DotNet_501_Runtime_Installed() then - AddProduct('dotnet-runtime-5.0.0-win' + GetArchitectureString(true) + '.exe', + AddProduct('dotnet-runtime-5.0.1-win' + GetArchitectureString(true) + '.exe', '/install /quiet /norestart', - GetString(CustomMessage('dotnet_500_runtime_title'), CustomMessage('dotnet_500_runtime_title_x64'), true), - GetString(CustomMessage('dotnet_500_runtime_size'), CustomMessage('dotnet_500_runtime_size_x64'), true), - GetString(CustomMessage('dotnet_500_runtime_url'), CustomMessage('dotnet_500_runtime_url_x64'), true), + GetString(CustomMessage('dotnet_501_runtime_title'), CustomMessage('dotnet_501_runtime_title_x64'), true), + GetString(CustomMessage('dotnet_501_runtime_size'), CustomMessage('dotnet_501_runtime_size_x64'), true), + GetString(CustomMessage('dotnet_501_runtime_url'), CustomMessage('dotnet_501_runtime_url_x64'), true), false, false, false); end; end; @@ -145,7 +198,7 @@ procedure dotnet_5_desktop; begin if not DotNet_5_Desktop_Installed() then begin - dotnet_501_desktop(); + dotnet_502_desktop(); { if no .NET 5 version installed then install the one above } end; end; @@ -154,7 +207,7 @@ procedure dotnet_5_runtime; begin if not DotNet_5_Runtime_Installed() then begin - dotnet_501_runtime(); + dotnet_502_runtime(); { if no .NET 5 version installed then install the one above } end; end; \ No newline at end of file From c11648cc8865918a29a186294327e85c95bd4448 Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Mon, 25 Jan 2021 21:55:24 +1300 Subject: [PATCH 10/14] Improvements to installer and uninstaller Post install option to Run the Tray App. Service stop, remove, install and start will wait a short while for the service to complete it. Tray App shutdown, Service Remove and Service Install happen at the correct phase of the installer. Uninstaller config remove prompt happens at correct phase during uninstall. --- DnsServiceSetup/Windows/DnsServiceSetup.iss | 9 +- DnsServiceSetup/Windows/DnsServiceSetup.pas | 133 ++++++++++++++++---- 2 files changed, 118 insertions(+), 24 deletions(-) diff --git a/DnsServiceSetup/Windows/DnsServiceSetup.iss b/DnsServiceSetup/Windows/DnsServiceSetup.iss index 0734f642..7710db54 100644 --- a/DnsServiceSetup/Windows/DnsServiceSetup.iss +++ b/DnsServiceSetup/Windows/DnsServiceSetup.iss @@ -9,6 +9,7 @@ #define TRAYAPP_FILENAME "DnsServerSystemTrayApp.exe" #define SERVICE_NAME "DnsService" +#define SERVICE_FILE "DnsService.exe" #define SERVICE_DISPLAY_NAME "Technitium DNS Server" #define SERVICE_DESCRIPTION "Technitium DNS Server" #define CONFIG_FOLDER "{app}\config" @@ -31,9 +32,8 @@ SetupIconFile=logo.ico WizardSmallImageFile=logo.bmp [Files] -Source: "{#TRAYAPP_LOCATION}\{#TRAYAPP_FILENAME}"; DestDir: "{app}"; BeforeInstall: KillTrayApp; -Source: "{#FILES_LOCATION}\*.*"; Excludes: "*.pdb,DnsService.exe"; DestDir: "{app}"; Flags: recursesubdirs; -Source: "{#FILES_LOCATION}\DnsService.exe"; DestDir: "{app}"; Flags: recursesubdirs; BeforeInstall: DoRemoveService; AfterInstall: DoInstallService; +Source: "{#TRAYAPP_LOCATION}\{#TRAYAPP_FILENAME}"; DestDir: "{app}"; +Source: "{#FILES_LOCATION}\*.*"; Excludes: "*.pdb"; DestDir: "{app}"; Flags: recursesubdirs; [Tasks] Name: "desktopicon"; Description: "Create an icon on the &desktop"; @@ -53,6 +53,9 @@ Root: HKCU; Subkey: "Software\{#COMPANY}"; Flags: uninsdeletekeyifempty Name: "{userprograms}\Technitium DNS Server"; Comment: "DNS Server Tray App"; Filename: "{app}\DnsServerSystemTrayApp.exe"; WorkingDir: "{app}\"; Flags: createonlyiffileexists Name: "{userdesktop}\Technitium DNS Server"; Filename: "{app}\DnsServerSystemTrayApp.exe"; WorkingDir: "{app}\"; Flags: createonlyiffileexists; Tasks: desktopicon +[Run] +Filename: "{app}\DnsServerSystemTrayApp.exe"; Description: "Run the Tray App"; Flags: postinstall nowait + ;Include the dependency code #include "depend\lang\english.iss" #include "depend\products.iss" diff --git a/DnsServiceSetup/Windows/DnsServiceSetup.pas b/DnsServiceSetup/Windows/DnsServiceSetup.pas index a77514f4..d04926d7 100644 --- a/DnsServiceSetup/Windows/DnsServiceSetup.pas +++ b/DnsServiceSetup/Windows/DnsServiceSetup.pas @@ -37,36 +37,113 @@ begin TaskKill('{#TRAYAPP_FILENAME}'); end; -procedure DoRemoveService(); //Removes the dns service from the scm +procedure DoStopService(); //Stops the dns service in the scm to allow it to update +var + stopCounter: Integer; + serviceStopped: Boolean; begin + stopCounter := 0; if IsServiceInstalled('{#SERVICE_NAME}') then begin Log('Service: Already installed'); if IsServiceRunning('{#SERVICE_NAME}') then begin - Log('Service: Already running'); + Log('Service: Already running, stopping service...'); StopService('{#SERVICE_NAME}'); - Sleep(3000); - end; - Log('Service: Remove'); - RemoveService('{#SERVICE_NAME}'); - Sleep(3000); + while IsServiceRunning('{#SERVICE_NAME}') do + begin + if stopCounter > 2 then begin + Log('Service: Waited too long to stop, killing task...'); + TaskKill('{#SERVICE_FILE}'); + Log('Service: Task killed'); + break; + end else begin + Log('Service: Waiting for stop'); + Sleep(2000); + stopCounter := stopCounter + 1 + end; + end; + if stopCounter < 3 then Log('Service: Stopped'); + end; end; end; -procedure DoInstallService(); //Adds the dns service to the scm +procedure DoRemoveService(); //Removes the dns service from the scm +var + stopCounter: Integer; +begin + stopCounter := 0; + if IsServiceInstalled('{#SERVICE_NAME}') then begin + Log('Service: Already installed, begin remove...'); + if IsServiceRunning('{#SERVICE_NAME}') then begin + Log('Service: Already running, stopping...'); + StopService('{#SERVICE_NAME}'); + while IsServiceRunning('{#SERVICE_NAME}') do + begin + if stopCounter > 2 then begin + Log('Service: Waited too long to stop, killing task...'); + TaskKill('{#SERVICE_FILE}'); + Log('Service: Task killed'); + break; + end else begin + Log('Service: Waiting for stop'); + Sleep(2000); + stopCounter := stopCounter + 1 + end; + end; + end; + + stopCounter := 0; + Log('Service: Removing...'); + RemoveService('{#SERVICE_NAME}'); + while IsServiceInstalled('{#SERVICE_NAME}') do + begin + if stopCounter > 2 then begin + Log('Service: Waited too long to remove, continuing'); + break; + end else begin + Log('Service: Waiting for removal'); + Sleep(2000); + stopCounter := stopCounter + 1 + end; + end; + if stopCounter < 3 then Log('Service: Removed'); + end; +end; + +procedure DoInstallService(); //Adds the dns service to the scm if not already installed var InstallSuccess: Boolean; + StartServiceSuccess: Boolean; MsgResult: Integer; + stopCounter: Integer; begin - Log('Service: Begin Install'); - InstallSuccess := InstallService(ExpandConstant('{app}\DnsService.exe'), '{#SERVICE_NAME}', '{#SERVICE_DISPLAY_NAME}', '{#SERVICE_DESCRIPTION}', SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START); - if not InstallSuccess then - begin - Log('Service: Install Fail ' + ServiceErrorToMessage(GetLastError())); - SuppressibleMsgBox(ExpandConstant('{cm:ServiceInstallFailure,' + ServiceErrorToMessage(GetLastError()) + '}'), mbCriticalError, MB_OK, IDOK); - end else begin - Log('Service: Install Success, Starting'); - StartService('{#SERVICE_NAME}'); + stopCounter := 0; + if IsServiceInstalled('{#SERVICE_NAME}') then begin + Log('Service: Already installed, skip install service'); + end else begin + Log('Service: Begin Install'); + InstallSuccess := InstallService(ExpandConstant('{app}\DnsService.exe'), '{#SERVICE_NAME}', '{#SERVICE_DISPLAY_NAME}', '{#SERVICE_DESCRIPTION}', SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START); + if not InstallSuccess then + begin + Log('Service: Install Fail ' + ServiceErrorToMessage(GetLastError())); + SuppressibleMsgBox(ExpandConstant('{cm:ServiceInstallFailure,' + ServiceErrorToMessage(GetLastError()) + '}'), mbCriticalError, MB_OK, IDOK); + end else begin + Log('Service: Install Success, Starting...'); + StartService('{#SERVICE_NAME}'); + + while IsServiceRunning('{#SERVICE_NAME}') <> true do + begin + if stopCounter > 3 then begin + Log('Service: Waited too long to start, continue'); + break; + end else begin + Log('Service: still starting') + Sleep(2000); + stopCounter := stopCounter + 1 + end; + end; + if stopCounter < 4 then Log('Service: Started'); + end; end; end; @@ -96,12 +173,26 @@ begin end; end; +procedure CurStepChanged(CurStep: TSetupStep); +begin + if CurStep = ssInstall then begin //Step happens just before installing files + KillTrayApp(); //Stop the tray app if running + DoRemoveService(); //Stop and remove the service if installed + end; + if CurStep = ssPostInstall then begin //Step happens just after installing files + DoInstallService(); //Install service after all files installed + end; +end; + procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); begin - if CurUninstallStep = usUninstall then + if CurUninstallStep = usUninstall then //Step happens before processing uninstall log begin - KillTrayApp(); - DoRemoveService(); - PromptRemoveConfiguration(); + KillTrayApp(); //Stop the tray app if running + DoRemoveService(); //Stop and remove the service + end; + if CurUninstallStep = usPostUninstall then //Step happens after processing uninstall log + begin + PromptRemoveConfiguration(); //Ask to remove any left over configuration files end; end; \ No newline at end of file From 4f78cafa215903e8a2bd06099536ca01f1a15a15 Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Tue, 23 Feb 2021 21:28:35 +1300 Subject: [PATCH 11/14] Set Service Configuration Folder Set service configuration folder to correct location. --- DnsService/DnsService.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/DnsService/DnsService.cs b/DnsService/DnsService.cs index 9879082b..8982f463 100644 --- a/DnsService/DnsService.cs +++ b/DnsService/DnsService.cs @@ -34,10 +34,25 @@ namespace DnsService protected override void OnStart(string[] args) { - _service = new WebService(null, new Uri("https://go.technitium.com/?id=22")); + _service = new WebService(getConfigFolder(), new Uri("https://go.technitium.com/?id=22")); _service.Start(); } + /// + /// If the service file path has the configuration files, use it, otherwise use the localapppath folder. + /// + /// The configuration path. + private string getConfigFolder() + { + string userConfigFolder = String.Concat(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"\Technitium\DNSServer"); + + if (System.IO.Directory.Exists(Environment.CurrentDirectory)) + { + return Environment.CurrentDirectory; + } + return userConfigFolder; + } + protected override void OnStop() { _service.Dispose(); From 0b00acafdddc868e155119c166f81295d20b3fb7 Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Tue, 23 Feb 2021 21:30:15 +1300 Subject: [PATCH 12/14] Fix .NET 5.0.2 url --- DnsServiceSetup/Windows/depend/products/dotnet5.iss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DnsServiceSetup/Windows/depend/products/dotnet5.iss b/DnsServiceSetup/Windows/depend/products/dotnet5.iss index 0ea36f79..adee3b22 100644 --- a/DnsServiceSetup/Windows/depend/products/dotnet5.iss +++ b/DnsServiceSetup/Windows/depend/products/dotnet5.iss @@ -3,15 +3,15 @@ dotnet_502_desktop_title=.NET 5.0.2 32-Bit Desktop Runtime dotnet_502_desktop_title_x64=.NET 5.0.2 64-Bit Desktop Runtime dotnet_502_desktop_size=47.1 MB dotnet_502_desktop_size_x64=52.5 MB -dotnet_502_desktop_url=https://download.visualstudio.microsoft.com/download/pr/adeb8933-7480-4015-abf6-ca31137ad7cd/1123096ebfa5ee3f36d77500b622e4d8/windowsdesktop-runtime-5.0.2-win-x86.exe -dotnet_502_desktop_url_x64=https://download.visualstudio.microsoft.com/download/pr/deffc9d5-ef77-4697-ac6e-33a58ccdc409/8386e478b5823a765dc1361155360877/windowsdesktop-runtime-5.0.2-win-x64.exe +dotnet_502_desktop_url=http://download.visualstudio.microsoft.com/download/pr/adeb8933-7480-4015-abf6-ca31137ad7cd/1123096ebfa5ee3f36d77500b622e4d8/windowsdesktop-runtime-5.0.2-win-x86.exe +dotnet_502_desktop_url_x64=http://download.visualstudio.microsoft.com/download/pr/deffc9d5-ef77-4697-ac6e-33a58ccdc409/8386e478b5823a765dc1361155360877/windowsdesktop-runtime-5.0.2-win-x64.exe dotnet_502_runtime_title=.NET 5.0.2 32-Bit Runtime dotnet_502_runtime_title_x64=.NET 5.0.2 64-Bit Runtime dotnet_502_runtime_size=22.8 MB dotnet_502_runtime_size_x64=25.3 MB -dotnet_502_runtime_url=https://download.visualstudio.microsoft.com/download/pr/46f8a025-ea67-4288-8e6a-709cfebc9b4b/69045dd85bcbfdd50441a87dedc13bd0/dotnet-runtime-5.0.2-win-x86.exe -dotnet_502_runtime_url_x64=https://download.visualstudio.microsoft.com/download/pr/8526b5e6-e6e7-4d0b-902b-1f4ad5dd1462/7de15b8c6ab0387402d5958e99a62ed9/dotnet-runtime-5.0.2-win-x64.exe +dotnet_502_runtime_url=http://download.visualstudio.microsoft.com/download/pr/46f8a025-ea67-4288-8e6a-709cfebc9b4b/69045dd85bcbfdd50441a87dedc13bd0/dotnet-runtime-5.0.2-win-x86.exe +dotnet_502_runtime_url_x64=http://download.visualstudio.microsoft.com/download/pr/8526b5e6-e6e7-4d0b-902b-1f4ad5dd1462/7de15b8c6ab0387402d5958e99a62ed9/dotnet-runtime-5.0.2-win-x64.exe dotnet_501_desktop_title=.NET 5.0.1 32-Bit Desktop Runtime From 70f7e29a6e1c74838912639d027c7bbf9660342b Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Tue, 23 Feb 2021 21:36:48 +1300 Subject: [PATCH 13/14] Further Install Refinements Add Config Migration Add Legacy Install Uninstaller Improve Pre and Post Un/install status display Install to PF32 Add Support and Publisher URL Remove Registry section --- DnsServiceSetup/Windows/DnsServiceSetup.iss | 22 +-- DnsServiceSetup/Windows/DnsServiceSetup.pas | 149 +++++++++++++++++++- DnsServiceSetup/Windows/depend/products.pas | 3 + 3 files changed, 161 insertions(+), 13 deletions(-) diff --git a/DnsServiceSetup/Windows/DnsServiceSetup.iss b/DnsServiceSetup/Windows/DnsServiceSetup.iss index 7710db54..8f523c8d 100644 --- a/DnsServiceSetup/Windows/DnsServiceSetup.iss +++ b/DnsServiceSetup/Windows/DnsServiceSetup.iss @@ -3,8 +3,9 @@ #define PRODUCT_VERSION "5.6" #define COMPANY "Technitium" #define TITLE "Technitium DNS Server" +#define APP_URL "https://technitium.com/dns/" -#define FILES_LOCATION "..\..\DnsService\bin\Release" +#define FILES_LOCATION "..\..\DnsService\bin\Release\net5.0-windows7.0" #define TRAYAPP_LOCATION "..\..\DnsServerSystemTrayApp\obj\Release" #define TRAYAPP_FILENAME "DnsServerSystemTrayApp.exe" @@ -12,18 +13,25 @@ #define SERVICE_FILE "DnsService.exe" #define SERVICE_DISPLAY_NAME "Technitium DNS Server" #define SERVICE_DESCRIPTION "Technitium DNS Server" -#define CONFIG_FOLDER "{app}\config" +#define CONFIG_FOLDER_COMPANY "{localappdata}\Technitium" +#define CONFIG_FOLDER_FULL CONFIG_FOLDER_COMPANY + "\DNS Server" + +#define LEGACY_INSTALLER_APPID "{9B86AC7F-53B3-4E31-B245-D4602D16F5C8}" +#define LEGACY_INSTALLER_CONFIG_PATH "{commonpf32}\Technitium\DNS Server\config" [Setup] PrivilegesRequired=admin AppName={#TITLE} AppVersion={#PRODUCT_VERSION} AppId={#APPID} -DefaultDirName={commonpf}\{#COMPANY}\{#PRODUCT_NAME} +DefaultDirName={commonpf32}\{#COMPANY}\{#PRODUCT_NAME} DefaultGroupName={#COMPANY} DisableProgramGroupPage=yes AppCopyright=Copyright (c) 2021 {#COMPANY} AppPublisher={#COMPANY} +AppSupportURL={#APP_URL} +AppPublisherURL={#APP_URL} + OutputDir=..\Release OutputBaseFilename=DnsServiceSetup CloseApplications=no @@ -33,7 +41,7 @@ WizardSmallImageFile=logo.bmp [Files] Source: "{#TRAYAPP_LOCATION}\{#TRAYAPP_FILENAME}"; DestDir: "{app}"; -Source: "{#FILES_LOCATION}\*.*"; Excludes: "*.pdb"; DestDir: "{app}"; Flags: recursesubdirs; +Source: "{#FILES_LOCATION}\*.*"; Excludes: "*.pdb,*.runtimeconfig.dev.json"; DestDir: "{app}"; Flags: recursesubdirs; [Tasks] Name: "desktopicon"; Description: "Create an icon on the &desktop"; @@ -45,16 +53,12 @@ ServiceInstallFailure=The DNS Service could not be installed. %1 ServiceManagerUnavailable=The Service Manager is not available! DependenciesDir=. -[Registry] -Root: HKLM; Subkey: "Software\{#COMPANY}"; Flags: uninsdeletekeyifempty -Root: HKCU; Subkey: "Software\{#COMPANY}"; Flags: uninsdeletekeyifempty - [Icons] Name: "{userprograms}\Technitium DNS Server"; Comment: "DNS Server Tray App"; Filename: "{app}\DnsServerSystemTrayApp.exe"; WorkingDir: "{app}\"; Flags: createonlyiffileexists Name: "{userdesktop}\Technitium DNS Server"; Filename: "{app}\DnsServerSystemTrayApp.exe"; WorkingDir: "{app}\"; Flags: createonlyiffileexists; Tasks: desktopicon [Run] -Filename: "{app}\DnsServerSystemTrayApp.exe"; Description: "Run the Tray App"; Flags: postinstall nowait +Filename: "{app}\DnsServerSystemTrayApp.exe"; Description: "Run the Tray App"; Flags: postinstall nowait; ;Include the dependency code #include "depend\lang\english.iss" diff --git a/DnsServiceSetup/Windows/DnsServiceSetup.pas b/DnsServiceSetup/Windows/DnsServiceSetup.pas index d04926d7..17e20381 100644 --- a/DnsServiceSetup/Windows/DnsServiceSetup.pas +++ b/DnsServiceSetup/Windows/DnsServiceSetup.pas @@ -12,6 +12,23 @@ begin RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and (Value <> ''); end; +function IsLegacyInstallerInstalled: Boolean; +var + Value: string; + UninstallKey: string; +begin + UninstallKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#LEGACY_INSTALLER_APPID}'; + Result := (RegQueryStringValue(HKLM, UninstallKey, 'UninstallString', Value) or + RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and (Value <> ''); +end; + +function IsLegacyConfigAvailable: Boolean; +var + Value: string; +begin + Result := DirExists(ExpandConstant('{#LEGACY_INSTALLER_CONFIG_PATH}')); +end; + //Skips the Task selection screen if an upgrade install function ShouldSkipPage(PageID: Integer): Boolean; begin @@ -22,6 +39,15 @@ function InitializeSetup(): boolean; begin //Specify the dependencies to install here dotnet_5_desktop(); + if IsLegacyInstallerInstalled or IsLegacyConfigAvailable then begin + AdditionalMemo := AdditionalMemo + #13#10 + #13#10 + 'Previous Version:'; + end; + if IsLegacyInstallerInstalled then begin + AdditionalMemo := AdditionalMemo + #13#10 + ' Remove Legacy Installer'; + end; + if IsLegacyConfigAvailable then begin + AdditionalMemo := AdditionalMemo + #13#10 + ' Migrate Configuration'; + end; Result := true; end; @@ -32,6 +58,14 @@ begin Exec(ExpandConstant('taskkill.exe'), '/f /im ' + '"' + fileName + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end; +function MsiExecUnins(appId: String): Integer; +var + ResultCode: Integer; +begin + ShellExec('', 'msiexec.exe', '/x ' + appId + ' /qn', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Result := ResultCode; +end; + procedure KillTrayApp; //Kill the tray app. Inno Setup cannot seem to close it through the "Close Applications" dialog. begin TaskKill('{#TRAYAPP_FILENAME}'); @@ -147,19 +181,109 @@ begin end; end; +procedure UninstallLegacyInstaller; +var + ResultCode: Integer; +begin + if IsLegacyInstallerInstalled then begin + Log('Uninstall MSI installer item'); + ResultCode := MsiExecUnins('{#LEGACY_INSTALLER_APPID}'); + Log('Result code ' + IntToStr(ResultCode)); + end; +end; + procedure RemoveConfiguration(); //Removes the configuration left by the DNS Server var DeleteSuccess: Boolean; begin Log('Delete configuration folder'); - DeleteSuccess := DelTree(ExpandConstant('{#CONFIG_FOLDER}'), True, True, True); + DeleteSuccess := DelTree(ExpandConstant('{#CONFIG_FOLDER_FULL}'), True, True, True); if not DeleteSuccess then begin - Log('Not all configuration files were deleted succesfully in ' + ExpandConstant('{#CONFIG_FOLDER}')); + Log('Not all configuration files were deleted succesfully in ' + ExpandConstant('{#CONFIG_FOLDER_FULL}')); SuppressibleMsgBox(ExpandConstant('{cm:RemoveConfigFail}'), mbError, MB_OK, IDOK); end; end; +procedure DirectoryCopy(SourcePath, DestPath: string); +var + FindRec: TFindRec; + SourceFilePath: string; + DestFilePath: string; +begin + if FindFirst(SourcePath + '\*', FindRec) then + begin + try + repeat + if (FindRec.Name <> '.') and (FindRec.Name <> '..') then + begin + SourceFilePath := SourcePath + '\' + FindRec.Name; + DestFilePath := DestPath + '\' + FindRec.Name; + if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0 then + begin + if FileCopy(SourceFilePath, DestFilePath, False) then + begin + Log(Format('Copied %s to %s', [SourceFilePath, DestFilePath])); + end + else + begin + Log(Format('Failed to copy %s to %s', [SourceFilePath, DestFilePath])); + end; + end + else + begin + if DirExists(DestFilePath) or CreateDir(DestFilePath) then + begin + Log(Format('Created %s', [DestFilePath])); + DirectoryCopy(SourceFilePath, DestFilePath); + end + else + begin + Log(Format('Failed to create %s', [DestFilePath])); + end; + end; + end; + until not FindNext(FindRec); + finally + FindClose(FindRec); + end; + end + else + begin + Log(Format('Failed to list %s', [SourcePath])); + end; +end; + +procedure MigrateConfiguration(); +var + ConfigDirExists : Boolean; +begin + + if IsLegacyConfigAvailable then begin + Log('Begin Configuration Migration'); + + ConfigDirExists := DirExists(ExpandConstant('{#CONFIG_FOLDER_COMPANY}')); + + if not ConfigDirExists then begin + Log('Create config folder company'); + CreateDir(ExpandConstant('{#CONFIG_FOLDER_COMPANY}')); + end; + + ConfigDirExists := DirExists(ExpandConstant('{#CONFIG_FOLDER_FULL}')); + + if not ConfigDirExists then begin + Log('Create config folder program'); + CreateDir(ExpandConstant('{#CONFIG_FOLDER_FULL}')); + end; + + DirectoryCopy(ExpandConstant('{#LEGACY_INSTALLER_CONFIG_PATH}'), ExpandConstant('{#CONFIG_FOLDER_FULL}')); + + DelTree(ExpandConstant('{#LEGACY_INSTALLER_CONFIG_PATH}'), true, true, true); + + Log('Complete Configuration Migration'); + end; +end; + procedure PromptRemoveConfiguration(); //Asks users if they want their config removed. On unattended installs, will keep config unless /removeconfig=true is supplied begin case ExpandConstant('{param:removeconfig|prompt}') of @@ -176,11 +300,26 @@ end; procedure CurStepChanged(CurStep: TSetupStep); begin if CurStep = ssInstall then begin //Step happens just before installing files + WizardForm.StatusLabel.Caption := 'Stopping Tray App...'; KillTrayApp(); //Stop the tray app if running - DoRemoveService(); //Stop and remove the service if installed + + if IsLegacyInstallerInstalled or IsLegacyConfigAvailable then begin + WizardForm.StatusLabel.Caption := 'Stopping Service...'; + DoStopService(); //Stop the service if running + + WizardForm.StatusLabel.Caption := 'Removing Legacy Installer...'; + UninstallLegacyInstaller(); //Uninstall Legacy Installer if Installed already + + WizardForm.StatusLabel.Caption := 'Migrating Configuration...'; + MigrateConfiguration(); //Shift configuration into correct path + end else begin + WizardForm.StatusLabel.Caption := 'Uninstalling Service...'; + DoRemoveService(); //Stop and remove the service if installed + end; end; if CurStep = ssPostInstall then begin //Step happens just after installing files - DoInstallService(); //Install service after all files installed + WizardForm.StatusLabel.Caption := 'Installing Service...'; + DoInstallService(); //Install service after all files installed, if not a portable install end; end; @@ -188,7 +327,9 @@ procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); begin if CurUninstallStep = usUninstall then //Step happens before processing uninstall log begin + UninstallProgressForm.StatusLabel.Caption := 'Stopping Tray App...'; KillTrayApp(); //Stop the tray app if running + UninstallProgressForm.StatusLabel.Caption := 'Uninstalling Service...'; DoRemoveService(); //Stop and remove the service end; if CurUninstallStep = usPostUninstall then //Step happens after processing uninstall log diff --git a/DnsServiceSetup/Windows/depend/products.pas b/DnsServiceSetup/Windows/depend/products.pas index 4d13bf9d..b040bfc9 100644 --- a/DnsServiceSetup/Windows/depend/products.pas +++ b/DnsServiceSetup/Windows/depend/products.pas @@ -18,6 +18,7 @@ var products: array of TProduct; delayedReboot, isForcedX86: boolean; DependencyPage: TOutputProgressWizardPage; + AdditionalMemo: string; procedure AddProduct(filename, parameters, title, size, url: string; forceSuccess, installClean, mustRebootAfter : boolean); { @@ -228,6 +229,8 @@ begin if MemoTasksInfo <> '' then s := s + MemoTasksInfo; + s := s + AdditionalMemo; + Result := s end; From 48c6bfe3f07b0f67581b0ec690458ac5cd59ba65 Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Tue, 23 Feb 2021 21:52:22 +1300 Subject: [PATCH 14/14] Installer Code Tidy up Move installer code into additional files and add method comments --- DnsServiceSetup/Windows/DnsServiceSetup.pas | 170 ++++---------------- DnsServiceSetup/Windows/helper.pas | 91 +++++++++++ DnsServiceSetup/Windows/legacy.pas | 73 +++++++++ 3 files changed, 194 insertions(+), 140 deletions(-) create mode 100644 DnsServiceSetup/Windows/helper.pas create mode 100644 DnsServiceSetup/Windows/legacy.pas diff --git a/DnsServiceSetup/Windows/DnsServiceSetup.pas b/DnsServiceSetup/Windows/DnsServiceSetup.pas index 17e20381..9e7ce1e8 100644 --- a/DnsServiceSetup/Windows/DnsServiceSetup.pas +++ b/DnsServiceSetup/Windows/DnsServiceSetup.pas @@ -1,35 +1,11 @@ //Include the sc functionality #include "service.pas" +#include "helper.pas" +#include "legacy.pas" -function IsUpgrade: Boolean; //Check to see if the install is an upgrade -var - Value: string; - UninstallKey: string; -begin - UninstallKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + - ExpandConstant('{#SetupSetting("AppId")}') + '_is1'; - Result := (RegQueryStringValue(HKLM, UninstallKey, 'UninstallString', Value) or - RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and (Value <> ''); -end; - -function IsLegacyInstallerInstalled: Boolean; -var - Value: string; - UninstallKey: string; -begin - UninstallKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#LEGACY_INSTALLER_APPID}'; - Result := (RegQueryStringValue(HKLM, UninstallKey, 'UninstallString', Value) or - RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and (Value <> ''); -end; - -function IsLegacyConfigAvailable: Boolean; -var - Value: string; -begin - Result := DirExists(ExpandConstant('{#LEGACY_INSTALLER_CONFIG_PATH}')); -end; - -//Skips the Task selection screen if an upgrade install +{ + Skips the tasks page if it is an upgrade install +} function ShouldSkipPage(PageID: Integer): Boolean; begin Result := (PageID = wpSelectTasks) and IsUpgrade; @@ -51,27 +27,18 @@ begin Result := true; end; -procedure TaskKill(fileName: String); //Kills an app by its filename -var - ResultCode: Integer; -begin - Exec(ExpandConstant('taskkill.exe'), '/f /im ' + '"' + fileName + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); -end; - -function MsiExecUnins(appId: String): Integer; -var - ResultCode: Integer; -begin - ShellExec('', 'msiexec.exe', '/x ' + appId + ' /qn', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); - Result := ResultCode; -end; - -procedure KillTrayApp; //Kill the tray app. Inno Setup cannot seem to close it through the "Close Applications" dialog. +{ + Kills the tray app +} +procedure KillTrayApp; begin TaskKill('{#TRAYAPP_FILENAME}'); end; -procedure DoStopService(); //Stops the dns service in the scm to allow it to update +{ + Stops the service +} +procedure DoStopService(); var stopCounter: Integer; serviceStopped: Boolean; @@ -101,7 +68,10 @@ begin end; end; -procedure DoRemoveService(); //Removes the dns service from the scm +{ + Removes the service from the computer +} +procedure DoRemoveService(); var stopCounter: Integer; begin @@ -144,7 +114,10 @@ begin end; end; -procedure DoInstallService(); //Adds the dns service to the scm if not already installed +{ + Installs the service onto the computer +} +procedure DoInstallService(); var InstallSuccess: Boolean; StartServiceSuccess: Boolean; @@ -181,18 +154,10 @@ begin end; end; -procedure UninstallLegacyInstaller; -var - ResultCode: Integer; -begin - if IsLegacyInstallerInstalled then begin - Log('Uninstall MSI installer item'); - ResultCode := MsiExecUnins('{#LEGACY_INSTALLER_APPID}'); - Log('Result code ' + IntToStr(ResultCode)); - end; -end; - -procedure RemoveConfiguration(); //Removes the configuration left by the DNS Server +{ + Removes the generated configuration +} +procedure RemoveConfiguration(); var DeleteSuccess: Boolean; begin @@ -205,86 +170,11 @@ begin end; end; -procedure DirectoryCopy(SourcePath, DestPath: string); -var - FindRec: TFindRec; - SourceFilePath: string; - DestFilePath: string; -begin - if FindFirst(SourcePath + '\*', FindRec) then - begin - try - repeat - if (FindRec.Name <> '.') and (FindRec.Name <> '..') then - begin - SourceFilePath := SourcePath + '\' + FindRec.Name; - DestFilePath := DestPath + '\' + FindRec.Name; - if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0 then - begin - if FileCopy(SourceFilePath, DestFilePath, False) then - begin - Log(Format('Copied %s to %s', [SourceFilePath, DestFilePath])); - end - else - begin - Log(Format('Failed to copy %s to %s', [SourceFilePath, DestFilePath])); - end; - end - else - begin - if DirExists(DestFilePath) or CreateDir(DestFilePath) then - begin - Log(Format('Created %s', [DestFilePath])); - DirectoryCopy(SourceFilePath, DestFilePath); - end - else - begin - Log(Format('Failed to create %s', [DestFilePath])); - end; - end; - end; - until not FindNext(FindRec); - finally - FindClose(FindRec); - end; - end - else - begin - Log(Format('Failed to list %s', [SourcePath])); - end; -end; - -procedure MigrateConfiguration(); -var - ConfigDirExists : Boolean; -begin - - if IsLegacyConfigAvailable then begin - Log('Begin Configuration Migration'); - - ConfigDirExists := DirExists(ExpandConstant('{#CONFIG_FOLDER_COMPANY}')); - - if not ConfigDirExists then begin - Log('Create config folder company'); - CreateDir(ExpandConstant('{#CONFIG_FOLDER_COMPANY}')); - end; - - ConfigDirExists := DirExists(ExpandConstant('{#CONFIG_FOLDER_FULL}')); - - if not ConfigDirExists then begin - Log('Create config folder program'); - CreateDir(ExpandConstant('{#CONFIG_FOLDER_FULL}')); - end; - - DirectoryCopy(ExpandConstant('{#LEGACY_INSTALLER_CONFIG_PATH}'), ExpandConstant('{#CONFIG_FOLDER_FULL}')); - - DelTree(ExpandConstant('{#LEGACY_INSTALLER_CONFIG_PATH}'), true, true, true); - - Log('Complete Configuration Migration'); - end; -end; - -procedure PromptRemoveConfiguration(); //Asks users if they want their config removed. On unattended installs, will keep config unless /removeconfig=true is supplied +{ + Prompts to remove the configuration + On unattended installs, will keep config unless /removeconfig=true is supplied +} +procedure PromptRemoveConfiguration(); begin case ExpandConstant('{param:removeconfig|prompt}') of 'prompt': diff --git a/DnsServiceSetup/Windows/helper.pas b/DnsServiceSetup/Windows/helper.pas new file mode 100644 index 00000000..8978e286 --- /dev/null +++ b/DnsServiceSetup/Windows/helper.pas @@ -0,0 +1,91 @@ +{ + Helper functions +} + +{ + Checks to see if the installer is an 'upgrade' +} +function IsUpgrade: Boolean; +var + Value: string; + UninstallKey: string; +begin + UninstallKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + + ExpandConstant('{#SetupSetting("AppId")}') + '_is1'; + Result := (RegQueryStringValue(HKLM, UninstallKey, 'UninstallString', Value) or + RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and (Value <> ''); +end; + +{ + Kills a running program by its filename +} +procedure TaskKill(fileName: String); +var + ResultCode: Integer; +begin + Exec(ExpandConstant('taskkill.exe'), '/f /im ' + '"' + fileName + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); +end; + + +{ + Executes the MSI Uninstall by GUID functionality +} +function MsiExecUnins(appId: String): Integer; +var + ResultCode: Integer; +begin + ShellExec('', 'msiexec.exe', '/x ' + appId + ' /qn', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Result := ResultCode; +end; + +{ + Copies and entire folder +} +procedure DirectoryCopy(SourcePath, DestPath: string); +var + FindRec: TFindRec; + SourceFilePath: string; + DestFilePath: string; +begin + if FindFirst(SourcePath + '\*', FindRec) then + begin + try + repeat + if (FindRec.Name <> '.') and (FindRec.Name <> '..') then + begin + SourceFilePath := SourcePath + '\' + FindRec.Name; + DestFilePath := DestPath + '\' + FindRec.Name; + if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0 then + begin + if FileCopy(SourceFilePath, DestFilePath, False) then + begin + Log(Format('Copied %s to %s', [SourceFilePath, DestFilePath])); + end + else + begin + Log(Format('Failed to copy %s to %s', [SourceFilePath, DestFilePath])); + end; + end + else + begin + if DirExists(DestFilePath) or CreateDir(DestFilePath) then + begin + Log(Format('Created %s', [DestFilePath])); + DirectoryCopy(SourceFilePath, DestFilePath); + end + else + begin + Log(Format('Failed to create %s', [DestFilePath])); + end; + end; + end; + until not FindNext(FindRec); + finally + FindClose(FindRec); + end; + end + else + begin + Log(Format('Failed to list %s', [SourcePath])); + end; +end; \ No newline at end of file diff --git a/DnsServiceSetup/Windows/legacy.pas b/DnsServiceSetup/Windows/legacy.pas new file mode 100644 index 00000000..84dc3905 --- /dev/null +++ b/DnsServiceSetup/Windows/legacy.pas @@ -0,0 +1,73 @@ +{ + Legacy Installer Functionality +} + +{ + Checks if the MSI Installer is installed +} +function IsLegacyInstallerInstalled: Boolean; +var + Value: string; + UninstallKey: string; +begin + UninstallKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#LEGACY_INSTALLER_APPID}'; + Result := (RegQueryStringValue(HKLM, UninstallKey, 'UninstallString', Value) or + RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and (Value <> ''); +end; + +{ + Checks if Configuration exists in the old location. +} +function IsLegacyConfigAvailable: Boolean; +var + Value: string; +begin + Result := DirExists(ExpandConstant('{#LEGACY_INSTALLER_CONFIG_PATH}')); +end; + +{ + Uninstalls Legacy Installer +} +procedure UninstallLegacyInstaller; +var + ResultCode: Integer; +begin + if IsLegacyInstallerInstalled then begin + Log('Uninstall MSI installer item'); + ResultCode := MsiExecUnins('{#LEGACY_INSTALLER_APPID}'); + Log('Result code ' + IntToStr(ResultCode)); + end; +end; + +{ + Migrates the Configuration to the new location +} +procedure MigrateConfiguration(); +var + ConfigDirExists : Boolean; +begin + + if IsLegacyConfigAvailable then begin + Log('Begin Configuration Migration'); + + ConfigDirExists := DirExists(ExpandConstant('{#CONFIG_FOLDER_COMPANY}')); + + if not ConfigDirExists then begin + Log('Create config folder company'); + CreateDir(ExpandConstant('{#CONFIG_FOLDER_COMPANY}')); + end; + + ConfigDirExists := DirExists(ExpandConstant('{#CONFIG_FOLDER_FULL}')); + + if not ConfigDirExists then begin + Log('Create config folder program'); + CreateDir(ExpandConstant('{#CONFIG_FOLDER_FULL}')); + end; + + DirectoryCopy(ExpandConstant('{#LEGACY_INSTALLER_CONFIG_PATH}'), ExpandConstant('{#CONFIG_FOLDER_FULL}')); + + DelTree(ExpandConstant('{#LEGACY_INSTALLER_CONFIG_PATH}'), true, true, true); + + Log('Complete Configuration Migration'); + end; +end; \ No newline at end of file