-
Notifications
You must be signed in to change notification settings - Fork 2
Setup for ASP.NET Core application
yurislav edited this page Oct 15, 2020
·
6 revisions
This is an example of creating installer for ASP.NET Core application with:
- windows service
- firewall exception
- software dependencies installation
- .NET Core and Hosting
- Windows hotfixes (KB)
- Multilanguage support: English and Slovak (sk-SK) in this example
Create new .NET Framework console applicaton and install following nuget packages:
The code example below expects Assets
folder in project directory with following structure:
├───Assets
│ │ background.bmp
│ │ banner.bmp
│ │ icon.ico
│ │ license.rtf
│ │
│ ├───Bootstrapper
│ │ │ bootstrapper-logo-64x64.png
│ │ │
│ │ ├───Dependencies
│ │ │ NDP452-KB2901907-x86-x64-AllOS-ENU
│ │ │ dotnet-hosting-3.1.3-win.exe
│ │ │ Windows6.1-KB2533623-x64.msu
│ │ │ Windows6.1-KB2533623-x86.msu
│ │ │ Windows6.1-KB2999226-x64.msu
│ │ │ Windows6.1-KB2999226-x86.msu
│ │ │ Windows8.1-KB3118401-x64.msu
│ │ │ Windows8.1-KB3118401-x86.msu
│ │ │
│ │ └───Localizations
│ │ thm.sk-SK.wxl
│ │
│ ├───Files
│ │ └───ProgramMenu
│ │ Online docs.url
│ │
│ └───Localizations
│ WixUI_sk-SK.wxl
Online dependencies must be stored locally on your machine during compiling the installer. Dependencies used in this example can be downloaded here:
Paste and customize following content to the Program.cs
file:
using NineDigit.WixSharpExtensions;
using NineDigit.WixSharpExtensions.Expressions;
using NineDigit.WixSharpExtensions.Localization;
using NineDigit.WixSharpExtensions.Resources;
using System;
using System.IO;
using System.Windows;
using WixSharp;
using WixSharp.Bootstrapper;
namespace WixsharpExtensionsExample
{
public class Program
{
// Company
const string CompanyFullName = "My company, inc.";
const string CompanyFolderName = "MyCompany";
// Product
const string ProductFullName = "My App";
const string ProductDescription = "My Application";
const string ProductComments = "My application comment";
const string ProductFolderName = "MyApp";
const string ProductProgramMenuFolderName = "My Application"; // name of folder in start menu
const string ProductHelpUrl = @"https://www.myapp.example/help";
const string ProductAboutUrl = @"https://www.myapp.example/about";
const string ProductContact = @"[email protected]";
readonly static Guid ProductUpgradeCode = new Guid("PUT-YOUR-PRODUCT-GUID-HERE"); // same value for all versions; must not be changed
readonly static string ProductLicenceRTFFilePath = Path.Combine("Assets", "license.rtf");
readonly static string ProductIconFilePath = Path.Combine("Assets", "icon.ico");
// Product Service
const string ServiceName = "My App service name";
const string ServiceDescription = "My App service description";
const string ServiceDisplayName = "My App service display name";
const string ServiceExecutableFileName = "MyApp.exe";
// Product Installer
readonly static string InstallerBackgroundImagePath = Path.Combine("Assets", "background.bmp");
readonly static string InstallerBannerImagePath = Path.Combine("Assets", "banner.bmp");
const string InstallerOutputFileNamePrefix = "my-app-v"; // name of .msi installer file
// Signing certificate
const string CertificateThumbprint = "INSERT-YOUR-CERTIFICATE-THUMPRINT";
const string CertificateTimestampUrl = @"http://timestamp-server.example";
// Bootstrapper
readonly static Guid BootstrapperUpgradeCode = new Guid("PUT-YOUR-BOOTSTRAPPER-GUID-HERE");
readonly static string BootstrapperAssetsDirectoryPath = Path.Combine("Assets", "Bootstrapper");
readonly static string BootstrapperLogoFilePath = Path.Combine(BootstrapperAssetsDirectoryPath, "bootstrapper-logo-64x64.png");
readonly static string BootstrapperDependenciesDirectoryPath = Path.Combine(BootstrapperAssetsDirectoryPath, "Dependencies"); // Path to bootstrapper dependencies on your computer
const string BootstrapperDependenciesDownloadUrlParentPath = @"https://www.myapp.example/dependencies"; // This is where bootstrapper dependencies will be stored for online installer
static void Main(string[] _)
{
var version = new Version(1, 0, 0); // you may want to get version from assembly dynamically with Tasks.GetVersionFromFile("path-to-your-app-assembly");
var isDebug = IsDebug();
var netCoreTargetVersion = new Version(3, 1);
var solutionDirectory = Directory.GetParent(Directory.GetCurrentDirectory()).FullName;
var sourceDirectoryPath = "my/app/publish/directory"; // directory where your app is published
var setupProjectDirectoryPath = Directory.GetCurrentDirectory();
var assetsFolderDirectoryPath = Path.Combine(setupProjectDirectoryPath, "Assets");
var filesFolderDirectoryPath = Path.Combine(assetsFolderDirectoryPath, "Files");
var filesProgramMenuDirectoryPath = Path.Combine(filesFolderDirectoryPath, "ProgramMenu");
var localizationsFolderDirectoryPath = Path.Combine(assetsFolderDirectoryPath, "Localizations");
var outputInstallerDirectoryPath = Path.Combine(solutionDirectory, "RELEASES");
var outputInstallerFileName = InstallerOutputFileNamePrefix + version.ToString(3);
var msiFilePath = new ManagedProject()
.SetProjectInfo(
upgradeCode: ProductUpgradeCode, // unique for this project; same value for all versions; must not be changed between versions.
name: ProductFullName,
description: ProductDescription,
version: version)
.SetControlPanelInfo(
name: ProductFullName,
manufacturer: CompanyFullName,
readme: ProductHelpUrl,
comment: ProductComments,
contact: ProductContact,
helpUrl: new Uri(ProductHelpUrl),
aboutUrl: new Uri(ProductAboutUrl),
productIconFilePath: new FileInfo(ProductIconFilePath))
.DisableDowngradeToPreviousVersion()
.AddDirectories(
// ProgramFiles folder must be specified **first**, only then it will be marked as "INSTALLDIR"
new Dir("%ProgramFiles%",
new Dir(CompanyFolderName,
new InstallDir(ProductFolderName,
new Files(
sourcePath: Path.Combine(sourceDirectoryPath, "*.*"),
filter: (filePath) => !filePath.EndsWithAny(true, ".pdb", ".obj", ".plist"))
)
)
),
new Dir("ProgramMenuFolder",
new Dir(ProductProgramMenuFolderName,
new Files(sourcePath: Path.Combine(filesProgramMenuDirectoryPath, "*.*"))
)
)
)
.AddWindowsServiceAndFirewallRule(
executableFileName: ServiceExecutableFileName,
name: ServiceName,
displayName: ServiceDisplayName,
description: ServiceDescription)
.SignWithCertificateThumprint(
certificateThumbprint: CertificateThumbprint,
signedContentDescription: ProductFullName,
timestampServerUrl: new Uri(CertificateTimestampUrl),
hashAlgorithm: HashAlgorithmType.sha256)
.SetMinimalUI(
backgroundImage: new FileInfo(InstallerBackgroundImagePath),
bannerImage: new FileInfo(InstallerBannerImagePath),
licenceRtfFile: new FileInfo(ProductLicenceRTFFilePath))
.SetOutputPath(
outputDir: new DirectoryInfo(outputInstallerDirectoryPath),
outputFileName: outputInstallerFileName)
.PreserveTempFiles(isDebug)
// Custom actions
.BindCustomAction<GreetingsCustomAction>()
// build the main MSI
.BuildMultilanguageMsi(
new ProjectLocalization("en-US"),
new ProjectLocalization(
language: "sk-SK",
localizationFile: Path.Combine(localizationsFolderDirectoryPath, "WixUI_sk-SK.wxl"),
downgradeErrorMessage: "Novšia verzia aplikácie [ProductName] je už na tomto počítači nainštalovaná. Ak si naozaj prajete nainštalovať staršiu verziu, prosím, najskôr odinštalujte túto aplikáciu. Inštalácia teraz skončí."));
// Bootstrapping (combine msi + its sw prerequisities)
const string dotNetCoreHostingDetectedVariableName = "DOT_NET_CORE_AND_HOSTING_DETECTED";
const string dotNetCoreHostingVersionVariableName = "DOT_NET_CORE_AND_HOSTING_VERSION";
const string win10OrNewerDetectedVariableName = "WINDOWS10_OR_NEWER_DETECTED";
const string dotNetFrameworkReleaseVersionVariableName = "DOT_NET_FRAMEWORK_RELEASE_VERSION";
var dotNetCoreHostingDetectedCondition = new WixExpression(dotNetCoreHostingDetectedVariableName);
var dotNetCoreHostingMinVersionInstalledCondition = WixExpression.Create(
new WixExpression(dotNetCoreHostingVersionVariableName),
WixComparativeExpressionOperator.Gte,
new WixExpression($"\"{netCoreTargetVersion.ToString(2)}\""));
var dotNetFramework45OrNewerInstalledCondition = WixExpression.Create(
new WixExpression(dotNetFrameworkReleaseVersionVariableName),
WixComparativeExpressionOperator.Gte,
new WixExpression(DotNeFrameworkReleaseMinimumVersion.DotNetFramework45.ToString()));
var win10OrNewerCondition = new WixExpression(win10OrNewerDetectedVariableName);
var win8or8dot1Condition = !win10OrNewerCondition & (WixExpression.OsVersion(WixComparativeExpressionOperator.Eq, VersionNT.Windows8OrServer2012) | WixExpression.OsVersion(WixComparativeExpressionOperator.Eq, VersionNT.Windows8dot1OrWindows10OrServer2012R2OrServer2016OrServer2019));
var win8or8dot1x64Condition = WixExpression.Is64BitOS() & win8or8dot1Condition;
var win8or8dot1x86Condition = WixExpression.Is32BitOS() & win8or8dot1Condition;
var win7Condition = WixExpression.OsVersion(WixComparativeExpressionOperator.Eq, VersionNT.Windows7OrWindows7Sp1OrServer2008R2);
var win7x64Condition = WixExpression.Is64BitOS() & win7Condition;
var win7x86Condition = WixExpression.Is32BitOS() & win7Condition;
var bootstrapper = new Bundle()
.SetInfo(
upgradeCode: BootstrapperUpgradeCode, // unique for this bootstrapper; same value for all versions; must not be changed between versions.
name: ProductFullName,
version: version)
.PreserveTempFiles(isDebug)
.SignWithCertificateThumprint(
certificateThumbprint: CertificateThumbprint,
signedContentDescription: ProductFullName,
timestampServerUrl: new Uri(CertificateTimestampUrl),
hashAlgorithm: HashAlgorithmType.sha256)
.HideFromAddRemovePrograms()
.SuppressApplicationOptionsUI()
.IncludeWixUtilExtension()
// request variables used in dependency conditions
.AddRegistrySearchAspNetCoreExists(dotNetCoreHostingDetectedVariableName)
.AddRegistrySearchAspNetCoreVersion(dotNetCoreHostingVersionVariableName)
.AddRegistrySeachWindows10OrNewerDetected(win10OrNewerDetectedVariableName)
.AddRegistrySearchForDotNetFrameworkReleaseVersion(dotNetFrameworkReleaseVersionVariableName)
// define dependencies
.AddOnlineDependency<ExePackage>("NDP452-KB2901907-x86-x64-AllOS-ENU.exe", // install .NET Framework 4.5 if missin on computer
BootstrapperDependenciesDirectoryPath, BootstrapperDependenciesDownloadUrlParentPath,
installCondition: !dotNetFramework45OrNewerInstalledCondition)
.AddOnlineDependency<MsuPackage>("Windows6.1-KB2533623-x64.msu",
BootstrapperDependenciesDirectoryPath, BootstrapperDependenciesDownloadUrlParentPath,
installCondition: win7x64Condition)
.AddOnlineDependency<MsuPackage>("Windows6.1-KB2533623-x86.msu",
BootstrapperDependenciesDirectoryPath, BootstrapperDependenciesDownloadUrlParentPath,
installCondition: win7x86Condition)
.AddOnlineDependency<MsuPackage>("Windows6.1-KB2999226-x64.msu",
BootstrapperDependenciesDirectoryPath, BootstrapperDependenciesDownloadUrlParentPath,
installCondition: win7x64Condition)
.AddOnlineDependency<MsuPackage>("Windows6.1-KB2999226-x86.msu",
BootstrapperDependenciesDirectoryPath, BootstrapperDependenciesDownloadUrlParentPath,
installCondition: win7x86Condition)
.AddOnlineDependency<MsuPackage>("Windows8.1-KB3118401-x64.msu",
BootstrapperDependenciesDirectoryPath, BootstrapperDependenciesDownloadUrlParentPath,
installCondition: win8or8dot1x64Condition)
.AddOnlineDependency<MsuPackage>("Windows8.1-KB3118401-x86.msu",
BootstrapperDependenciesDirectoryPath, BootstrapperDependenciesDownloadUrlParentPath,
installCondition: win8or8dot1x86Condition)
.AddOnlineDependency<ExePackage>("dotnet-hosting-3.1.3-win.exe",
BootstrapperDependenciesDirectoryPath, BootstrapperDependenciesDownloadUrlParentPath,
installCondition: !(dotNetCoreHostingDetectedCondition & dotNetCoreHostingMinVersionInstalledCondition),
exeExitCodeMap: new ExitCodeMapBuilder().Add(ResultWin32.ERROR_PRODUCT_VERSION, BehaviorValues.success).Build()) // Ignore the "Newer version installed" error
.AddRollbackBoundary()
.AddLocalDependency<MsiPackage>(msiFilePath, isRequired: true, msiVisibleInAddRemoveProgramsMenu: true, msiDisplayInternalUI: true);
bootstrapper.Application.LogoFile = BootstrapperLogoFilePath;
bootstrapper.Application.AddLocalization(new BootstrapperAppLocalization("sk-SK", Path.Combine(BootstrapperAssetsDirectoryPath, "Localizations", "thm.sk-SK.wxl")));
// building online bundle
var onlineExeFilePath = msiFilePath.PathChangeExtension(".online.exe");
bootstrapper.Build(onlineExeFilePath);
// building offline bundle
var offlineExeFilePath = msiFilePath.PathChangeExtension(".offline.exe");
bootstrapper
.SetAllOnlineDependenciesToLocal()
.Build(offlineExeFilePath);
}
private static bool IsDebug()
{
#if DEBUG
return true;
#else
return false;
#endif
}
}
class GreetingsCustomAction : CustomAction, ILoadAware, IBeforeInstallAware, IAfterInstallAware
{
public void OnLoad(SetupEventArgs e)
{
MessageBox.Show("Hello from OnLoad event handler!");
}
public void OnBeforeInstallFiles(SetupEventArgs e)
{
MessageBox.Show("Hello from Before install event handler!");
}
public void AfterInstallFiles(SetupEventArgs e)
{
MessageBox.Show("Hello from After install event handler!");
}
}
}
Content of WixUI_sk-SK.wxl
file:
<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="sk-SK" Codepage="1250" xmlns="http://schemas.microsoft.com/wix/2006/localization">
<String Id="msierrFirewallCannotConnect" Overridable="yes">Cannot connect to Windows Firewall. ([2] [3] [4] [5])</String>
<String Id="WixSchedFirewallExceptionsInstall" Overridable="yes">Configuring Windows Firewall</String>
<String Id="WixSchedFirewallExceptionsUninstall" Overridable="yes">Configuring Windows Firewall</String>
<String Id="WixRollbackFirewallExceptionsInstall" Overridable="yes">Rolling back Windows Firewall configuration</String>
<String Id="WixExecFirewallExceptionsInstall" Overridable="yes">Installing Windows Firewall configuration</String>
<String Id="WixRollbackFirewallExceptionsUninstall" Overridable="yes">Rolling back Windows Firewall configuration</String>
<String Id="WixExecFirewallExceptionsUninstall" Overridable="yes">Uninstalling Windows Firewall configuration</String>
</WixLocalization>
Content of thm.sk-SK.wxl
file:
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
<WixLocalization Culture="sk-sk" Language="1051" xmlns="http://schemas.microsoft.com/wix/2006/localization">
<String Id="Caption">[WixBundleName] – inštalácia</String>
<String Id="Title">[WixBundleName]</String>
<String Id="InstallHeader">Welcome</String>
<String Id="InstallMessage">Setup will install [WixBundleName] on your computer. Click install to continue, options to set the install directory or Close to exit.</String>
<String Id="InstallVersion">Version [WixBundleVersion]</String>
<String Id="ConfirmCancelMessage">Naozaj chcete zrušiť operáciu?</String>
<String Id="ExecuteUpgradeRelatedBundleMessage">Previous version</String>
<String Id="HelpHeader">Pomocník pre inštaláciu</String>
<String Id="HelpText">/install | /repair | /uninstall | /layout [directory] - nainštaluje, opraví, odinštaluje
alebo vytvorí kompletnú lokálnu kópiu balíčka v adresári. Predvolená je možnosť Install.
/passive | /quiet – zobrazí minimálne používateľské rozhranie bez výziev alebo
nezobrazí žiadne používateľské rozhranie ani výzvy. Predvolene sa
zobrazuje používateľské rozhranie aj všetky výzvy.
/norestart – zruší všetky pokusy o reštart. Používateľské rozhranie
predvolene zobrazí pred reštartom výzvu.
/log log.txt – urobí záznam do určeného súboru. Súbor denníka sa predvolene
vytvorí v priečinku %TEMP%.</String>
<String Id="HelpCloseButton">&Zavrieť</String>
<String Id="InstallLicenseLinkText">[WixBundleName] <a href="#">Licenčná zmluva</a>.</String>
<String Id="InstallAcceptCheckbox">Súhlasím s podmienkami licenčnej zmluvy</String>
<String Id="InstallOptionsButton">&Možnosti</String>
<String Id="InstallInstallButton">&Inštalovať</String>
<String Id="InstallCloseButton">&Zavrieť</String>
<String Id="OptionsHeader">Možnosti inštalácie</String>
<String Id="OptionsLocationLabel">Cieľový adresár:</String>
<String Id="OptionsBrowseButton">&Prehľadávať</String>
<String Id="OptionsOkButton">&OK</String>
<String Id="OptionsCancelButton">&Zrušiť</String>
<String Id="ProgressHeader">Priebeh inštalácie</String>
<String Id="ProgressLabel">Spracúva sa:</String>
<String Id="OverallProgressPackageText">Spracúva sa...</String>
<String Id="ProgressCancelButton">&Zrušiť</String>
<String Id="ModifyHeader">Oprava inštalácie</String>
<String Id="ModifyRepairButton">&Opraviť</String>
<String Id="ModifyUninstallButton">&Odinštalovať</String>
<String Id="ModifyCloseButton">&Zavrieť</String>
<String Id="SuccessRepairHeader">Oprava úspešne dokončená</String>
<String Id="SuccessUninstallHeader">Odinštalácia úspešne dokončená</String>
<String Id="SuccessInstallHeader">Inštalácia úspešne dokončená</String>
<String Id="SuccessHeader">Inštalácia </String>
<String Id="SuccessLaunchButton">&Spustiť</String>
<String Id="SuccessRestartText">Pred použitím programu je potrebné reštartovať počítač.</String>
<String Id="SuccessRestartButton">&Reštartovať</String>
<String Id="SuccessCloseButton">&Dokončiť</String>
<String Id="FailureHeader">Inštalácia zlyhala</String>
<String Id="FailureInstallHeader">Inštalácia zlyhala</String>
<String Id="FailureUninstallHeader">Odinštalácia zlyhala</String>
<String Id="FailureRepairHeader">Oprava zlyhala</String>
<String Id="FailureHyperlinkLogText">Inštalácia zlyhala pre jednu alebo viac príčin. Odstráňte problémy a skúste znova spustiť inštaláciu. Ďalšie informácie nájdete v <a href="#">súbore denníka</a>.</String>
<String Id="FailureRestartText">Dokončenie všetkých zmien softvéru vyžaduje reštart počítača.</String>
<String Id="FailureRestartButton">&Reštartovať</String>
<String Id="FailureCloseButton">&Zavrieť</String>
<String Id="FilesInUseHeader">Používané spbory</String>
<String Id="FilesInUseLabel">Nasledujúce aplikácie používajú súbory, ktoré musí táto inštalácia aktualizovať:</String>
<String Id="FilesInUseCloseRadioButton">Zavrieť &aplikácie a pokúsiť sa ich reštartovať neskôr.</String>
<String Id="FilesInUseDontCloseRadioButton">&Nezatvárať aplikácie. Bude potrebný reštart systému.</String>
<String Id="FilesInUseOkButton">&OK</String>
<String Id="FilesInUseCancelButton">&Zrušiť</String>
<String Id="ErrorFailNoActionReboot">Žiadna akcia nebola vykonaná, pretože je potrebný reštart systému.</String>
</WixLocalization>
Application will produce following files:
File name | Original source |
---|---|
my-app-v1.0.0.msi | MSI product installer that will install only your app on the machine, without dependencies (.net core and windows KB's) |
my-app-v1.0.0.offline.exe | Bootstrapper application that installs all software dependencies and your product. Dependencies are embedded, no internet connection is required during installation. |
my-app-v1.0.0.online.exe | Bootstrapper application that installs all software dependencies and your product. Dependencies are downloaded from online location, if installing condition is met. |