diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7fd2e0d677..7ff59702a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,11 +33,9 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 6.0.x - - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.3 + dotnet-version: 7.0.x - name: Build - run: msbuild src -p:Configuration=Release -restore -m -verbosity:minimal + run: dotnet build src --configuration Release -graph - name: Zip PowerShell module run: | New-Item assets\PowerShellModules -ItemType Directory diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f1403c4be..9621317fc7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,11 +28,11 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 6.0.x - - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.3 + dotnet-version: 7.0.x - name: Build - run: msbuild src -p:Configuration=Release -restore -m -verbosity:minimal + run: dotnet build src --configuration Release -graph + - name: Build ServiceControl Management + run: dotnet publish src\ServiceControl.Config\ServiceControl.Config.csproj --configuration Release --runtime win-x64 --no-build --output assets - name: Sign NuGet packages uses: Particular/sign-nuget-packages-action@v1.0.0 with: @@ -44,6 +44,20 @@ jobs: run: New-FileCatalog -Path deploy\PowerShellModules\Particular.ServiceControl.Management -CatalogFilePath deploy\PowerShellModules\Particular.ServiceControl.Management\Particular.ServiceControl.Management.cat -CatalogVersion 2.0 - name: Install AzureSignTool run: dotnet tool install --global azuresigntool + - name: Sign ServiceControl Management EXE + run: | + AzureSignTool sign ` + --file-digest sha256 ` + --timestamp-rfc3161 http://timestamp.digicert.com ` + --azure-key-vault-url https://particularcodesigning.vault.azure.net ` + --azure-key-vault-client-id ${{ secrets.AZURE_KEY_VAULT_CLIENT_ID }} ` + --azure-key-vault-tenant-id ${{ secrets.AZURE_KEY_VAULT_TENANT_ID }} ` + --azure-key-vault-client-secret ${{ secrets.AZURE_KEY_VAULT_CLIENT_SECRET }} ` + --azure-key-vault-certificate ${{ secrets.AZURE_KEY_VAULT_CERTIFICATE_NAME }} ` + assets\Particular.ServiceControl.exe + - name: Rename ServiceControl Management EXE + run: Rename-Item -Path assets\Particular.ServiceControl.exe -NewName "Particular.ServiceControl-${{ env.MinVerVersion }}.exe" + shell: pwsh - name: Sign PowerShell module run: | AzureSignTool sign ` @@ -59,20 +73,6 @@ jobs: run: | New-Item assets\PowerShellModules -ItemType Directory Compress-Archive -Path deploy\PowerShellModules\Particular.ServiceControl.Management\* -DestinationPath assets\PowerShellModules\Particular.ServiceControl.Management.zip - - name: Setup Advanced Installer - run: | - $version = "20.2.1" - choco install advanced-installer --version=$version - & "C:\Program Files (x86)\Caphyon\Advanced Installer $version\bin\x86\AdvancedInstaller.com" /register ${{ secrets.ADVANCED_INSTALLER_LICENSE_KEY }} - - name: Prepare AIP file - run: | - $content = Get-Content -Raw -Path src/Setup/ServiceControl.aip - $content = $content -replace "replace-tenant-id", "${{ secrets.AZURE_KEY_VAULT_TENANT_ID }}" -replace "replace-app-id", "${{ secrets.AZURE_KEY_VAULT_CLIENT_ID }}" -replace "replace-cert-name", "${{ secrets.AZURE_KEY_VAULT_CERTIFICATE_NAME }}" - Set-Content src/Setup/ServiceControl.aip $content - - name: Build Windows installer - env: - AZURE_KEY_VAULT_CLIENT_SECRET: ${{ secrets.AZURE_KEY_VAULT_CLIENT_SECRET }} - run: dotnet build src/Setup --configuration Release # - name: Build Docker images # if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }} # run: dotnet build src/ServiceControl.DockerImages --configuration Release @@ -105,9 +105,9 @@ jobs: $nugetsCount = (Get-ChildItem -Recurse -File nugets).Count $zipCount = (Get-ChildItem -Recurse -File zip).Count - $expectedAssetsCount = 2 - $expectedNugetsCount = 1 - $expectedZipCount = 3 + $expectedAssetsCount = 2 # SCMU & PowerShell module + $expectedNugetsCount = 1 # PlatformSample + $expectedZipCount = 3 # ServiceControl, Audit, & Monitoring if ($assetsCount -ne $expectedAssetsCount) { diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index fe5f0f89c6..2022e0f0a1 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -29,7 +29,7 @@ - + @@ -73,8 +73,7 @@ - - + diff --git a/src/ServiceControl.Audit.AcceptanceTests.RavenDB5/AcceptanceTestStorageConfiguration.cs b/src/ServiceControl.Audit.AcceptanceTests.RavenDB5/AcceptanceTestStorageConfiguration.cs index d841cc37ee..d2a73073be 100644 --- a/src/ServiceControl.Audit.AcceptanceTests.RavenDB5/AcceptanceTestStorageConfiguration.cs +++ b/src/ServiceControl.Audit.AcceptanceTests.RavenDB5/AcceptanceTestStorageConfiguration.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Threading; using System.Threading.Tasks; using ServiceControl.Audit.Persistence.RavenDb; using ServiceControl.Audit.Persistence.Tests; @@ -10,15 +11,17 @@ class AcceptanceTestStorageConfiguration { public string PersistenceType { get; protected set; } + EmbeddedDatabase databaseInstance; + string databaseName; + public async Task> CustomizeSettings() { - var databaseName = Guid.NewGuid().ToString(); - - var instance = await SharedEmbeddedServer.GetInstance(); + databaseName = Guid.NewGuid().ToString(); + databaseInstance = await SharedEmbeddedServer.GetInstance(); return new Dictionary { - { RavenDbPersistenceConfiguration.ConnectionStringKey,instance.ServerUrl }, + { RavenDbPersistenceConfiguration.ConnectionStringKey,databaseInstance.ServerUrl }, { RavenDbPersistenceConfiguration.DatabaseNameKey,databaseName} }; } @@ -30,9 +33,9 @@ public Task Configure() return Task.CompletedTask; } - public Task Cleanup() + public async Task Cleanup() { - return Task.CompletedTask; + await databaseInstance.DeleteDatabase(databaseName); } } } diff --git a/src/ServiceControl.Audit.Persistence.RavenDb5/EmbeddedDatabase.cs b/src/ServiceControl.Audit.Persistence.RavenDb5/EmbeddedDatabase.cs index a2cebf9eea..a89211028b 100644 --- a/src/ServiceControl.Audit.Persistence.RavenDb5/EmbeddedDatabase.cs +++ b/src/ServiceControl.Audit.Persistence.RavenDb5/EmbeddedDatabase.cs @@ -12,6 +12,7 @@ using NServiceBus.Logging; using Raven.Client.Documents; using Raven.Client.Documents.Conventions; + using Raven.Client.ServerWide.Operations; using Raven.Embedded; public class EmbeddedDatabase : IDisposable @@ -148,6 +149,14 @@ public async Task Connect(CancellationToken cancellationToken) return store; } + public async Task DeleteDatabase(string dbName) + { + using (var store = await EmbeddedServer.Instance.GetDocumentStoreAsync(new DatabaseOptions(dbName) { SkipCreatingDatabase = true })) + { + await store.Maintenance.Server.SendAsync(new DeleteDatabasesOperation(dbName, true)); + } + } + public void Dispose() { shutdownTokenSource.Cancel(); diff --git a/src/ServiceControl.Audit.Persistence.Tests.RavenDb5/ApprovalFiles/InstallationTests.Should_write_expected_config_file.approved.txt b/src/ServiceControl.Audit.Persistence.Tests.RavenDb5/ApprovalFiles/InstallationTests.Should_write_expected_config_file.approved.txt index 7a1bb7668d..9f1f2110a7 100644 --- a/src/ServiceControl.Audit.Persistence.Tests.RavenDb5/ApprovalFiles/InstallationTests.Should_write_expected_config_file.approved.txt +++ b/src/ServiceControl.Audit.Persistence.Tests.RavenDb5/ApprovalFiles/InstallationTests.Should_write_expected_config_file.approved.txt @@ -8,6 +8,8 @@ + diff --git a/src/ServiceControl.Audit.Persistence.Tests/ApprovalFiles/InstallationTests.Should_write_expected_config_file.approved.txt b/src/ServiceControl.Audit.Persistence.Tests/ApprovalFiles/InstallationTests.Should_write_expected_config_file.approved.txt index cfab9f072c..0635c70b45 100644 --- a/src/ServiceControl.Audit.Persistence.Tests/ApprovalFiles/InstallationTests.Should_write_expected_config_file.approved.txt +++ b/src/ServiceControl.Audit.Persistence.Tests/ApprovalFiles/InstallationTests.Should_write_expected_config_file.approved.txt @@ -8,6 +8,8 @@ + diff --git a/src/ServiceControl.Audit.Persistence.Tests/InstallationTests.cs b/src/ServiceControl.Audit.Persistence.Tests/InstallationTests.cs index 53a85e74cb..74bec7467d 100644 --- a/src/ServiceControl.Audit.Persistence.Tests/InstallationTests.cs +++ b/src/ServiceControl.Audit.Persistence.Tests/InstallationTests.cs @@ -1,6 +1,5 @@ namespace ServiceControl.Audit.Persistence.Tests { - using System; using System.IO; using System.Linq; using System.Text.Json; @@ -21,7 +20,7 @@ public void Should_write_expected_config_file() persistenceManifest = JsonSerializer.Deserialize(manifestContent); } - var newInstance = new ServiceControlAuditNewInstance(new Version(1, 0, 0), persistenceManifest); + var newInstance = new ServiceControlAuditNewInstance(persistenceManifest); var installPath = Path.Combine(Path.GetTempPath(), TestContext.CurrentContext.Test.ID, "install"); diff --git a/src/ServiceControl.Config.Tests/ServiceControl.Config.Tests.csproj b/src/ServiceControl.Config.Tests/ServiceControl.Config.Tests.csproj index 17b0e081bc..8d716d82d6 100644 --- a/src/ServiceControl.Config.Tests/ServiceControl.Config.Tests.csproj +++ b/src/ServiceControl.Config.Tests/ServiceControl.Config.Tests.csproj @@ -1,7 +1,8 @@  - net472 + net7.0-windows10.0.17763.0 + x64 @@ -9,12 +10,6 @@ - - - - - - diff --git a/src/ServiceControl.Config/App.xaml.cs b/src/ServiceControl.Config/App.xaml.cs index 50d270fd13..0fc6dd0363 100644 --- a/src/ServiceControl.Config/App.xaml.cs +++ b/src/ServiceControl.Config/App.xaml.cs @@ -2,8 +2,6 @@ { using System; using System.Diagnostics; - using System.IO; - using System.Reflection; using UI.MessageBox; public partial class App @@ -23,29 +21,12 @@ public App() [STAThread] public static void Main(string[] args) { - AppDomain.CurrentDomain.AssemblyResolve += (s, e) => ResolveAssembly(e.Name); - - Splash.Show(); if (args.Length == 0) { + Splash.Show(); var app = new App(); app.Run(); } } - - static Assembly ResolveAssembly(string name) - { - var assemblyLocation = Assembly.GetEntryAssembly().Location; - var appDirectory = Path.GetDirectoryName(assemblyLocation); - var requestingName = new AssemblyName(name).Name; - - var combine = Path.Combine(appDirectory, requestingName + ".dll"); - if (!File.Exists(combine)) - { - return null; - } - - return Assembly.LoadFrom(combine); - } } } \ No newline at end of file diff --git a/src/ServiceControl.Config/AppBootstrapper.cs b/src/ServiceControl.Config/AppBootstrapper.cs index 7345e2d5a7..981a819b21 100644 --- a/src/ServiceControl.Config/AppBootstrapper.cs +++ b/src/ServiceControl.Config/AppBootstrapper.cs @@ -11,6 +11,8 @@ using Caliburn.Micro; using FluentValidation; using ReactiveUI; + using ServiceControl.Config.Framework; + using ServiceControlInstaller.Engine.Validation; using UI.Shell; public class AppBootstrapper : BootstrapperBase @@ -87,6 +89,14 @@ protected override async void OnStartup(object sender, StartupEventArgs e) ValidatorOptions.Global.DefaultRuleLevelCascadeMode = CascadeMode.Stop; await DisplayRootViewForAsync(); + + if (DotnetVersionValidator.FrameworkRequirementsAreMissing(true, out var message)) + { + message += $"{Environment.NewLine}{Environment.NewLine}Until the prerequisites are installed, no ServiceControl instances can be installed."; + + var windowManager = GetInstance(typeof(IServiceControlWindowManager), null) as IServiceControlWindowManager; + await windowManager.ShowMessage("Missing prerequisites", message, acceptText: "I understand", hideCancel: true); + } } IContainer container; diff --git a/src/ServiceControl.Config/Commands/UpgradeAuditInstanceCommand.cs b/src/ServiceControl.Config/Commands/UpgradeAuditInstanceCommand.cs index d67ce0d696..1389654bc0 100644 --- a/src/ServiceControl.Config/Commands/UpgradeAuditInstanceCommand.cs +++ b/src/ServiceControl.Config/Commands/UpgradeAuditInstanceCommand.cs @@ -13,6 +13,7 @@ using ServiceControlInstaller.Engine.Configuration.ServiceControl; using ServiceControlInstaller.Engine.Instances; using ServiceControlInstaller.Engine.ReportCard; + using ServiceControlInstaller.Engine.Validation; using UI.InstanceDetails; class UpgradeAuditInstanceCommand : AwaitableAbstractCommand @@ -95,6 +96,12 @@ public override async Task ExecuteAsync(InstanceDetailsViewModel model) } } + if (DotnetVersionValidator.FrameworkRequirementsAreMissing(needsRavenDB: true, out var missingMessage)) + { + await windowManager.ShowMessage("Missing prerequisites", missingMessage, acceptText: "Cancel", hideCancel: true); + return; + } + if (instance.TransportPackage.IsOldRabbitMQTransport() && !await windowManager.ShowYesNoDialog("UPGRADE WARNING", $"ServiceControl version {serviceControlInstaller.ZipInfo.Version} requires RabbitMQ broker version 3.10.0 or higher. Also, the stream_queue and quorum_queue feature flags must be enabled on the broker. Please confirm your broker meets the minimum requirements before upgrading.", "Do you want to proceed?", diff --git a/src/ServiceControl.Config/Commands/UpgradeMonitoringInstanceCommand.cs b/src/ServiceControl.Config/Commands/UpgradeMonitoringInstanceCommand.cs index 24beb66df0..8a36729b9f 100644 --- a/src/ServiceControl.Config/Commands/UpgradeMonitoringInstanceCommand.cs +++ b/src/ServiceControl.Config/Commands/UpgradeMonitoringInstanceCommand.cs @@ -11,6 +11,7 @@ using ServiceControl.Engine.Extensions; using ServiceControlInstaller.Engine.Instances; using ServiceControlInstaller.Engine.ReportCard; + using ServiceControlInstaller.Engine.Validation; using UI.InstanceDetails; class UpgradeMonitoringInstanceCommand : AwaitableAbstractCommand @@ -46,6 +47,12 @@ public override async Task ExecuteAsync(InstanceDetailsViewModel model) instance.Service.Refresh(); + if (DotnetVersionValidator.FrameworkRequirementsAreMissing(needsRavenDB: false, out var missingMessage)) + { + await windowManager.ShowMessage("Missing prerequisites", missingMessage, acceptText: "Cancel", hideCancel: true); + return; + } + if (instance.TransportPackage.IsOldRabbitMQTransport() && !await windowManager.ShowYesNoDialog("UPGRADE WARNING", $"ServiceControl version {installer.ZipInfo.Version} requires RabbitMQ broker version 3.10.0 or higher. Also, the stream_queue and quorum_queue feature flags must be enabled on the broker. Please confirm your broker meets the minimum requirements before upgrading.", "Do you want to proceed?", diff --git a/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs b/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs index c3ffe299e1..4d6d44848a 100644 --- a/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs +++ b/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs @@ -224,6 +224,12 @@ await windowManager.ShowMessage("UPGRADE INCOMPATIBLE", } } + if (DotnetVersionValidator.FrameworkRequirementsAreMissing(needsRavenDB: true, out var missingMessage)) + { + await windowManager.ShowMessage("Missing prerequisites", missingMessage, acceptText: "Cancel", hideCancel: true); + return; + } + if (instance.TransportPackage.IsOldRabbitMQTransport() && !await windowManager.ShowYesNoDialog("UPGRADE WARNING", $"ServiceControl version {serviceControlInstaller.ZipInfo.Version} requires RabbitMQ broker version 3.10.0 or higher. Also, the stream_queue and quorum_queue feature flags must be enabled on the broker. Please confirm your broker meets the minimum requirements before upgrading.", "Do you want to proceed?", diff --git a/src/ServiceControl.Config/Extensions/FilePathExtensions.cs b/src/ServiceControl.Config/Extensions/FilePathExtensions.cs index 5ecbc92a46..439642c636 100644 --- a/src/ServiceControl.Config/Extensions/FilePathExtensions.cs +++ b/src/ServiceControl.Config/Extensions/FilePathExtensions.cs @@ -9,6 +9,12 @@ public static class FilePathExtensions { static bool TryGetPathRoot(string path, out string root) { + if (string.IsNullOrWhiteSpace(path)) + { + root = string.Empty; + return false; + } + try { root = Path.GetPathRoot(path); diff --git a/src/ServiceControl.Config/Framework/Modules/InstallerModule.cs b/src/ServiceControl.Config/Framework/Modules/InstallerModule.cs index 6be8fbee67..30588759a1 100644 --- a/src/ServiceControl.Config/Framework/Modules/InstallerModule.cs +++ b/src/ServiceControl.Config/Framework/Modules/InstallerModule.cs @@ -1,8 +1,6 @@ namespace ServiceControl.Config.Framework.Modules { using System; - using System.IO; - using System.Reflection; using System.Threading.Tasks; using Autofac; using ServiceControl.LicenseManagement; @@ -10,6 +8,7 @@ namespace ServiceControl.Config.Framework.Modules using ServiceControlInstaller.Engine.Instances; using ServiceControlInstaller.Engine.ReportCard; using ServiceControlInstaller.Engine.Validation; + using Constants = ServiceControlInstaller.Engine.Instances.Constants; using Module = Autofac.Module; public class InstallerModule : Module @@ -26,8 +25,7 @@ public class ServiceControlInstanceInstaller : ServiceControlInstallerBase { public ServiceControlInstanceInstaller() { - var appDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - ZipInfo = ServiceControlZipInfo.Find(appDirectory); + ZipInfo = new PlatformZipInfo(Constants.ServiceControlExe, "ServiceControl", "Particular.ServiceControl.zip", Constants.CurrentVersion); } } @@ -35,8 +33,7 @@ public class ServiceControlAuditInstanceInstaller : ServiceControlInstallerBase { public ServiceControlAuditInstanceInstaller() { - var appDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - ZipInfo = ServiceControlAuditZipInfo.Find(appDirectory); + ZipInfo = new PlatformZipInfo(Constants.ServiceControlAuditExe, "ServiceControl Audit", "Particular.ServiceControl.Audit.zip", Constants.CurrentVersion); } } @@ -60,7 +57,7 @@ internal async Task Add(ServiceControlInstallableBase details, IProg } progress.Report(3, 9, "Copying files..."); - instanceInstaller.CopyFiles(ZipInfo.FilePath); + instanceInstaller.CopyFiles(ZipInfo.ResourceName); progress.Report(4, 9, "Writing configurations..."); instanceInstaller.WriteConfigurationFile(); @@ -124,7 +121,7 @@ internal ReportCard Upgrade(ServiceControlBaseService instance, ServiceControlUp try { progress.Report(currentStep++, totalSteps, "Upgrading Files..."); - instance.UpgradeFiles(ZipInfo.FilePath); + instance.UpgradeFiles(ZipInfo.ResourceName); } finally { @@ -272,8 +269,7 @@ public class MonitoringInstanceInstaller { public MonitoringInstanceInstaller() { - var appDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - ZipInfo = MonitoringZipInfo.Find(appDirectory); + ZipInfo = new PlatformZipInfo(Constants.MonitoringExe, "ServiceControl Monitoring", "Particular.ServiceControl.Monitoring.zip", Constants.CurrentVersion); } public PlatformZipInfo ZipInfo { get; } @@ -294,7 +290,7 @@ internal async Task Add(MonitoringNewInstance details, IProgress prog try { progress.Report(2, 5, "Upgrading Files..."); - instance.UpgradeFiles(ZipInfo.FilePath); + instance.UpgradeFiles(ZipInfo.ResourceName); } finally { diff --git a/src/ServiceControl.Config/Framework/RaygunFeedBack.cs b/src/ServiceControl.Config/Framework/RaygunFeedBack.cs index 57187d6c8e..f2fb390d4d 100644 --- a/src/ServiceControl.Config/Framework/RaygunFeedBack.cs +++ b/src/ServiceControl.Config/Framework/RaygunFeedBack.cs @@ -2,9 +2,9 @@ { using System; using System.IO; + using System.Threading.Tasks; using Extensions; using Mindscape.Raygun4Net; - using Mindscape.Raygun4Net.Messages; public class RaygunFeedback : RaygunReporter { @@ -28,7 +28,7 @@ void InitializeTrackingId() } } - public void SendFeedBack(string emailAddress, string message, bool includeSystemInfo) + public Task SendFeedBack(string emailAddress, string message, bool includeSystemInfo) { raygunClient.UserInfo = new RaygunIdentifierMessage(trackingId.BareString()) { @@ -36,7 +36,7 @@ public void SendFeedBack(string emailAddress, string message, bool includeSystem UUID = trackingId.BareString() }; - var raygunMessage = RaygunMessageBuilder.New + var raygunMessage = RaygunMessageBuilder.New(new RaygunSettings()) .SetUser(raygunClient.UserInfo) .SetVersion(Version) .SetExceptionDetails(new Feedback(message)); @@ -48,17 +48,17 @@ public void SendFeedBack(string emailAddress, string message, bool includeSystem } var m = raygunMessage.Build(); - raygunClient.Send(m); + return raygunClient.Send(m); } - public void SendException(Exception ex, bool includeSystemInfo) + public Task SendException(Exception ex, bool includeSystemInfo) { raygunClient.UserInfo = new RaygunIdentifierMessage(trackingId.BareString()) { UUID = trackingId.BareString() }; - var raygunMessage = RaygunMessageBuilder.New + var raygunMessage = RaygunMessageBuilder.New(new RaygunSettings()) .SetUser(raygunClient.UserInfo) .SetVersion(Version) .SetExceptionDetails(ex); @@ -70,10 +70,9 @@ public void SendException(Exception ex, bool includeSystemInfo) } var m = raygunMessage.Build(); - raygunClient.Send(m); + return raygunClient.Send(m); } - RaygunClient raygunClient = new RaygunClient(RaygunApiKey); Guid trackingId = Guid.NewGuid(); } diff --git a/src/ServiceControl.Config/Framework/RaygunReporter.cs b/src/ServiceControl.Config/Framework/RaygunReporter.cs index 5177b37f12..050cd40e60 100644 --- a/src/ServiceControl.Config/Framework/RaygunReporter.cs +++ b/src/ServiceControl.Config/Framework/RaygunReporter.cs @@ -1,20 +1,22 @@ namespace ServiceControl.Config.Framework { + using System; using System.Net; + using System.Net.Http; using System.Reflection; using System.Threading.Tasks; using Extensions; using Mindscape.Raygun4Net; - public class RaygunReporter + public abstract class RaygunReporter { protected RaygunReporter() { - init = Task.Run(() => + init = Task.Run(async () => { - enabled = TestAndSetCreds(null) || - TestAndSetCreds(CredentialCache.DefaultCredentials) || - TestAndSetCreds(CredentialCache.DefaultNetworkCredentials); + enabled = (await TestAndSetCreds(null)) || + (await TestAndSetCreds(CredentialCache.DefaultCredentials)) || + (await TestAndSetCreds(CredentialCache.DefaultNetworkCredentials)); Version = GetVersion(); }); } @@ -34,25 +36,36 @@ public bool Enabled protected string Version { get; private set; } - bool TestAndSetCreds(ICredentials credentials) + async Task TestAndSetCreds(ICredentials credentials) { - var client = WebRequest.Create(RaygunUrl); + HttpClient http = null; try { - client.Timeout = 5000; - client.Proxy.Credentials = credentials; - using (client.GetResponse()) + http = new HttpClient(new HttpClientHandler { Credentials = credentials }) { - raygunClient.ProxyCredentials = credentials; - return true; - } + Timeout = TimeSpan.FromSeconds(5) + }; + using var response = await http.GetAsync(RaygunUrl); + response.EnsureSuccessStatusCode(); + + raygunClient = new RaygunClientWithCredentials(http); + return true; } catch { + http?.Dispose(); return false; } } + class RaygunClientWithCredentials : RaygunClient + { + public RaygunClientWithCredentials(HttpClient http) + : base(new RaygunSettings { ApiKey = RaygunApiKey }, http) + { + } + } + static string GetVersion() { var assemblyInfo = Assembly.GetExecutingAssembly().GetAttribute(); @@ -60,7 +73,7 @@ static string GetVersion() return versionParts?[0]; } - RaygunClient raygunClient = new RaygunClient(RaygunApiKey); + protected RaygunClient raygunClient = new RaygunClient(RaygunApiKey); Task init; bool enabled; protected const string RaygunApiKey = "zdm49nndHCXZ3NVzM8Kzug=="; diff --git a/src/ServiceControl.Config/ServiceControl.Config.csproj b/src/ServiceControl.Config/ServiceControl.Config.csproj index d80aaa0fd9..2eed6192e1 100644 --- a/src/ServiceControl.Config/ServiceControl.Config.csproj +++ b/src/ServiceControl.Config/ServiceControl.Config.csproj @@ -1,25 +1,22 @@ - + - net472 + net7.0-windows10.0.17763.0 WinExe - ServiceControlConfig + Particular.ServiceControl App.ico true app.manifest - 10.0 + true + true + true + true - - - - - - @@ -31,9 +28,9 @@ - + + - @@ -56,11 +53,4 @@ - - - - - - - diff --git a/src/ServiceControl.Config/UI/FeedBack/FeedBackViewModel.cs b/src/ServiceControl.Config/UI/FeedBack/FeedBackViewModel.cs index 35e8fa5956..ed627be581 100644 --- a/src/ServiceControl.Config/UI/FeedBack/FeedBackViewModel.cs +++ b/src/ServiceControl.Config/UI/FeedBack/FeedBackViewModel.cs @@ -42,7 +42,7 @@ async Task Send() try { - feedBack.SendFeedBack(EmailAddress, Message, IncludeSystemInfo); + await feedBack.SendFeedBack(EmailAddress, Message, IncludeSystemInfo); Success = true; } catch diff --git a/src/ServiceControl.Config/UI/InstanceAdd/MonitoringAddAttachment.cs b/src/ServiceControl.Config/UI/InstanceAdd/MonitoringAddAttachment.cs index 949adbc385..f2b4cb2dee 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/MonitoringAddAttachment.cs +++ b/src/ServiceControl.Config/UI/InstanceAdd/MonitoringAddAttachment.cs @@ -69,6 +69,13 @@ async Task Add() ServiceAccountPwd = viewModel.Password }; + if (DotnetVersionValidator.FrameworkRequirementsAreMissing(needsRavenDB: false, out var missingMessage)) + { + await windowManager.ShowMessage("Missing prerequisites", missingMessage, acceptText: "Cancel", hideCancel: true); + viewModel.InProgress = false; + return; + } + if (instanceMetadata.TransportPackage.IsLatestRabbitMQTransport() && !await windowManager.ShowYesNoDialog("INSTALL WARNING", $"ServiceControl version {installer.ZipInfo.Version} requires RabbitMQ broker version 3.10.0 or higher. Also, the stream_queue and quorum_queue feature flags must be enabled on the broker. Please confirm your broker meets the minimum requirements before installing.", "Do you want to proceed?", diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs index 5292038143..1c97e34868 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs +++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs @@ -100,6 +100,13 @@ async Task Add() auditNewInstance.EnableFullTextSearchOnBodies = viewModel.ServiceControlAudit.EnableFullTextSearchOnBodies.Value; } + if (DotnetVersionValidator.FrameworkRequirementsAreMissing(needsRavenDB: true, out var missingMessage)) + { + await windowManager.ShowMessage("Missing prerequisites", missingMessage, acceptText: "Cancel", hideCancel: true); + viewModel.InProgress = false; + return; + } + var transportPackage = serviceControlNewInstance != null ? serviceControlNewInstance.TransportPackage : auditNewInstance.TransportPackage; if (transportPackage.IsLatestRabbitMQTransport() && !await windowManager.ShowYesNoDialog("INSTALL WARNING", $"ServiceControl version {serviceControlInstaller.ZipInfo.Version} requires RabbitMQ broker version 3.10.0 or higher. Also, the stream_queue and quorum_queue feature flags must be enabled on the broker. Please confirm your broker meets the minimum requirements before installing.", diff --git a/src/ServiceControl.Config/UI/ListInstances/ListInstancesViewModel.cs b/src/ServiceControl.Config/UI/ListInstances/ListInstancesViewModel.cs index 3848bb0464..b71a2acdf7 100644 --- a/src/ServiceControl.Config/UI/ListInstances/ListInstancesViewModel.cs +++ b/src/ServiceControl.Config/UI/ListInstances/ListInstancesViewModel.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; - using System.Net; + using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Caliburn.Micro; @@ -45,13 +45,13 @@ public Task HandleAsync(LicenseUpdated licenseUpdatedEvent, CancellationToken ca continue; } - _ = Task.Run(() => + _ = Task.Run(async () => { try { - var request = WebRequest.Create($"{instance.BrowsableUrl}license?refresh=true"); - request.Timeout = 2000; - request.GetResponse(); + using var http = new HttpClient(); + http.Timeout = TimeSpan.FromSeconds(2); + await http.GetAsync($"{instance.BrowsableUrl}license?refresh=true"); } catch { diff --git a/src/ServiceControl.Config/UI/MessageBox/ExceptionMessageBox.xaml.cs b/src/ServiceControl.Config/UI/MessageBox/ExceptionMessageBox.xaml.cs index 9bf47c75a4..bfb921fa8e 100644 --- a/src/ServiceControl.Config/UI/MessageBox/ExceptionMessageBox.xaml.cs +++ b/src/ServiceControl.Config/UI/MessageBox/ExceptionMessageBox.xaml.cs @@ -103,7 +103,7 @@ async Task CallReportClick(object sender) try { - await Task.Factory.StartNew(() => reporter.SendException(Exception, IncludeSystemInfo)); + await reporter.SendException(Exception, IncludeSystemInfo); } finally { diff --git a/src/ServiceControl.Config/Xaml/Behaviours/BorderlessWindowBehavior.cs b/src/ServiceControl.Config/Xaml/Behaviours/BorderlessWindowBehavior.cs index 11d7ec8872..b7ea49c9ca 100644 --- a/src/ServiceControl.Config/Xaml/Behaviours/BorderlessWindowBehavior.cs +++ b/src/ServiceControl.Config/Xaml/Behaviours/BorderlessWindowBehavior.cs @@ -135,35 +135,35 @@ IntPtr HwndHook(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam, ref bool // check if the mouse is within one of the rectangles if (topLeft.Contains(windowPoint)) { - returnval = (IntPtr)NativeConstants.HTTOPLEFT; + returnval = NativeConstants.HTTOPLEFT; } else if (top.Contains(windowPoint)) { - returnval = (IntPtr)NativeConstants.HTTOP; + returnval = NativeConstants.HTTOP; } else if (topRight.Contains(windowPoint)) { - returnval = (IntPtr)NativeConstants.HTTOPRIGHT; + returnval = NativeConstants.HTTOPRIGHT; } else if (left.Contains(windowPoint)) { - returnval = (IntPtr)NativeConstants.HTLEFT; + returnval = NativeConstants.HTLEFT; } else if (right.Contains(windowPoint)) { - returnval = (IntPtr)NativeConstants.HTRIGHT; + returnval = NativeConstants.HTRIGHT; } else if (bottomLeft.Contains(windowPoint)) { - returnval = (IntPtr)NativeConstants.HTBOTTOMLEFT; + returnval = NativeConstants.HTBOTTOMLEFT; } else if (bottom.Contains(windowPoint)) { - returnval = (IntPtr)NativeConstants.HTBOTTOM; + returnval = NativeConstants.HTBOTTOM; } else if (bottomRight.Contains(windowPoint)) { - returnval = (IntPtr)NativeConstants.HTBOTTOMRIGHT; + returnval = NativeConstants.HTBOTTOMRIGHT; } if (returnval != IntPtr.Zero) diff --git a/src/ServiceControl.Config/Xaml/Behaviours/RichTextBoxHelper.cs b/src/ServiceControl.Config/Xaml/Behaviours/RichTextBoxHelper.cs index d1da6df4d0..45c71ac41b 100644 --- a/src/ServiceControl.Config/Xaml/Behaviours/RichTextBoxHelper.cs +++ b/src/ServiceControl.Config/Xaml/Behaviours/RichTextBoxHelper.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; using System.IO; + using System.Linq; using System.Text; + using System.Text.RegularExpressions; using System.Threading; using System.Windows; using System.Windows.Controls; @@ -24,6 +26,9 @@ public static void SetDocumentXaml(DependencyObject obj, string value) static HashSet recursionProtection = new HashSet(); + static readonly Regex doubleNewlineRegex = new Regex(@"\r?\n\r?\n", RegexOptions.Compiled); + static readonly Regex singleNewlineRegex = new Regex(@"\r?\n", RegexOptions.Compiled); + public static readonly DependencyProperty DocumentXamlProperty = DependencyProperty.RegisterAttached( "DocumentXaml", @@ -54,7 +59,13 @@ public static void SetDocumentXaml(DependencyObject obj, string value) if (!xaml.Contains("{xaml}"; + var paragraphs = doubleNewlineRegex.Split(xaml) + .Select(p => $"{singleNewlineRegex.Replace(p, "")}") + .ToArray(); + + xaml = paragraphs.Length > 1 + ? "
" + string.Join(string.Empty, paragraphs) + "
" + : paragraphs[0]; } using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/InvokeAuditUpgrade.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/InvokeAuditUpgrade.cs index 9d5f28df8d..ccfe45234f 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/InvokeAuditUpgrade.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/InvokeAuditUpgrade.cs @@ -6,6 +6,7 @@ namespace ServiceControl.Management.PowerShell using ServiceControl.Engine.Extensions; using ServiceControlInstaller.Engine.Instances; using ServiceControlInstaller.Engine.Unattended; + using ServiceControlInstaller.Engine.Validation; using Validation; [Cmdlet(VerbsLifecycle.Invoke, "ServiceControlAuditInstanceUpgrade")] @@ -31,8 +32,7 @@ protected override void ProcessRecord() { var logger = new PSLogger(Host); - var zipFolder = ZipPath.Get(this); - var installer = new UnattendAuditInstaller(logger, zipFolder); + var installer = new UnattendAuditInstaller(logger); foreach (var name in Name) { @@ -48,6 +48,11 @@ protected override void ProcessRecord() instance.EnableFullTextSearchOnBodies = false; } + if (DotnetVersionValidator.FrameworkRequirementsAreMissing(needsRavenDB: true, out var missingMessage)) + { + ThrowTerminatingError(new ErrorRecord(new Exception(missingMessage), "Missing Prerequisites", ErrorCategory.NotInstalled, null)); + } + if (instance.TransportPackage.IsOldRabbitMQTransport() && (Acknowledgements == null || !Acknowledgements.Any(ack => ack.Equals(AcknowledgementValues.RabbitMQBrokerVersion310, StringComparison.OrdinalIgnoreCase)))) { diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/NewServiceControlAuditInstance.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/NewServiceControlAuditInstance.cs index 2e9c21a934..6bd08fe0ce 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/NewServiceControlAuditInstance.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/NewServiceControlAuditInstance.cs @@ -156,10 +156,14 @@ protected override void ProcessRecord() newAuditInstance.ServiceControlQueueAddress = ServiceControlQueueAddress; newAuditInstance.EnableFullTextSearchOnBodies = EnableFullTextSearchOnBodies; - var zipfolder = ZipPath.Get(this); var logger = new PSLogger(Host); - var installer = new UnattendAuditInstaller(logger, zipfolder); + var installer = new UnattendAuditInstaller(logger); + + if (DotnetVersionValidator.FrameworkRequirementsAreMissing(needsRavenDB: true, out var missingMessage)) + { + ThrowTerminatingError(new ErrorRecord(new Exception(missingMessage), "Missing Prerequisites", ErrorCategory.NotInstalled, null)); + } if (newAuditInstance.TransportPackage.IsLatestRabbitMQTransport() && (Acknowledgements == null || !Acknowledgements.Any(ack => ack.Equals(AcknowledgementValues.RabbitMQBrokerVersion310, StringComparison.OrdinalIgnoreCase)))) diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/RemoveServiceControlAuditInstance.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/RemoveServiceControlAuditInstance.cs index 36309969fa..e0ff5d0ddb 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/RemoveServiceControlAuditInstance.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/RemoveServiceControlAuditInstance.cs @@ -28,8 +28,7 @@ protected override void BeginProcessing() protected override void ProcessRecord() { var logger = new PSLogger(Host); - var zipfolder = ZipPath.Get(this); - var installer = new UnattendAuditInstaller(logger, zipfolder); + var installer = new UnattendAuditInstaller(logger); foreach (var name in Name) { diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/InvokeMonitoringUpgrade.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/InvokeMonitoringUpgrade.cs index c3617e1c6b..2801098b94 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/InvokeMonitoringUpgrade.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/InvokeMonitoringUpgrade.cs @@ -6,6 +6,7 @@ namespace ServiceControl.Management.PowerShell using ServiceControl.Engine.Extensions; using ServiceControlInstaller.Engine.Instances; using ServiceControlInstaller.Engine.Unattended; + using ServiceControlInstaller.Engine.Validation; using Validation; [Cmdlet(VerbsLifecycle.Invoke, "MonitoringInstanceUpgrade")] @@ -28,8 +29,7 @@ protected override void ProcessRecord() { var logger = new PSLogger(Host); - var zipFolder = ZipPath.Get(this); - var installer = new UnattendMonitoringInstaller(logger, zipFolder); + var installer = new UnattendMonitoringInstaller(logger); foreach (var name in Name) { @@ -40,6 +40,11 @@ protected override void ProcessRecord() break; } + if (DotnetVersionValidator.FrameworkRequirementsAreMissing(needsRavenDB: false, out var missingMessage)) + { + ThrowTerminatingError(new ErrorRecord(new Exception(missingMessage), "Missing Prerequisites", ErrorCategory.NotInstalled, null)); + } + if (instance.TransportPackage.IsOldRabbitMQTransport() && (Acknowledgements == null || !Acknowledgements.Any(ack => ack.Equals(AcknowledgementValues.RabbitMQBrokerVersion310, StringComparison.OrdinalIgnoreCase)))) { diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/NewMonitoringInstance.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/NewMonitoringInstance.cs index 53d83b5781..79af5a1dee 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/NewMonitoringInstance.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/NewMonitoringInstance.cs @@ -126,10 +126,14 @@ protected override void ProcessRecord() }; var details = monitoringNewInstance; - var zipfolder = ZipPath.Get(this); var logger = new PSLogger(Host); - var installer = new UnattendMonitoringInstaller(logger, zipfolder); + var installer = new UnattendMonitoringInstaller(logger); + + if (DotnetVersionValidator.FrameworkRequirementsAreMissing(needsRavenDB: false, out var missingMessage)) + { + ThrowTerminatingError(new ErrorRecord(new Exception(missingMessage), "Missing Prerequisites", ErrorCategory.NotInstalled, null)); + } if (details.TransportPackage.IsLatestRabbitMQTransport() && (Acknowledgements == null || !Acknowledgements.Any(ack => ack.Equals(AcknowledgementValues.RabbitMQBrokerVersion310, StringComparison.OrdinalIgnoreCase)))) diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/RemoveMonitoringInstance.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/RemoveMonitoringInstance.cs index 36ebcefe81..dd8beb31b7 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/RemoveMonitoringInstance.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/RemoveMonitoringInstance.cs @@ -25,8 +25,7 @@ protected override void BeginProcessing() protected override void ProcessRecord() { var logger = new PSLogger(Host); - var zipfolder = ZipPath.Get(this); - var installer = new UnattendMonitoringInstaller(logger, zipfolder); + var installer = new UnattendMonitoringInstaller(logger); foreach (var name in Name) { diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/AddServiceControlRemote.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/AddServiceControlRemote.cs index 0eb525ad34..2db0d4b87a 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/AddServiceControlRemote.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/AddServiceControlRemote.cs @@ -22,8 +22,7 @@ protected override void ProcessRecord() { var logger = new PSLogger(Host); - var zipFolder = ZipPath.Get(this); - var installer = new UnattendServiceControlInstaller(logger, zipFolder); + var installer = new UnattendServiceControlInstaller(logger); var instance = InstanceFinder.FindInstanceByName(Name); if (instance == null) diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/InvokeServiceControlInstanceUpgrade.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/InvokeServiceControlInstanceUpgrade.cs index 8742e29b59..9e2b4e0acb 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/InvokeServiceControlInstanceUpgrade.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/InvokeServiceControlInstanceUpgrade.cs @@ -66,8 +66,7 @@ protected override void ProcessRecord() { var logger = new PSLogger(Host); - var zipFolder = ZipPath.Get(this); - var installer = new UnattendServiceControlInstaller(logger, zipFolder); + var installer = new UnattendServiceControlInstaller(logger); var instance = InstanceFinder.FindInstanceByName(Name); if (instance == null) @@ -84,7 +83,7 @@ protected override void ProcessRecord() PerformUpgrade(instance, installer); break; case RequiredUpgradeAction.SplitOutAudit: - PerformSplit(instance, logger, zipFolder).Wait(); + PerformSplit(instance, logger).Wait(); break; case RequiredUpgradeAction.ConvertToAudit: default: @@ -93,11 +92,11 @@ protected override void ProcessRecord() } } - async Task PerformSplit(ServiceControlInstance instance, PSLogger logger, string zipFolder) + async Task PerformSplit(ServiceControlInstance instance, PSLogger logger) { AssertValidForAuditSplit(instance.Name); - var serviceControlSplitter = new UnattendServiceControlSplitter(logger, zipFolder); + var serviceControlSplitter = new UnattendServiceControlSplitter(logger); var options = new UnattendServiceControlSplitter.Options { @@ -158,6 +157,11 @@ void PerformUpgrade(ServiceControlInstance instance, UnattendServiceControlInsta UpgradeControl.GetUpgradeInfoForTargetVersion(installer.ZipInfo.Version, instance.Version), }; + if (DotnetVersionValidator.FrameworkRequirementsAreMissing(needsRavenDB: true, out var missingMessage)) + { + ThrowTerminatingError(new ErrorRecord(new Exception(missingMessage), "Missing Prerequisites", ErrorCategory.NotInstalled, null)); + } + if (instance.TransportPackage.IsOldRabbitMQTransport() && (Acknowledgements == null || !Acknowledgements.Any(ack => ack.Equals(AcknowledgementValues.RabbitMQBrokerVersion310, StringComparison.OrdinalIgnoreCase)))) { diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs index d9c1b968fc..3090336cef 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs @@ -171,12 +171,16 @@ protected override void ProcessRecord() details.EnableFullTextSearchOnBodies = EnableFullTextSearchOnBodies; var modulePath = Path.GetDirectoryName(MyInvocation.MyCommand.Module.Path); - var zipfolder = ZipPath.Get(this); var logger = new PSLogger(Host); - var installer = new UnattendServiceControlInstaller(logger, zipfolder); + var installer = new UnattendServiceControlInstaller(logger); try { + if (DotnetVersionValidator.FrameworkRequirementsAreMissing(needsRavenDB: true, out var missingMessage)) + { + ThrowTerminatingError(new ErrorRecord(new Exception(missingMessage), "Missing Prerequisites", ErrorCategory.NotInstalled, null)); + } + if (details.TransportPackage.IsLatestRabbitMQTransport() && (Acknowledgements == null || !Acknowledgements.Any(ack => ack.Equals(AcknowledgementValues.RabbitMQBrokerVersion310, StringComparison.OrdinalIgnoreCase)))) { @@ -184,7 +188,6 @@ protected override void ProcessRecord() } logger.Info("Module root at " + modulePath); - logger.Info("Installer(s) path at " + zipfolder); logger.Info("Installing Service Control instance..."); var result = installer.Add(details, PromptToProceed); result.Wait(); diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/RemoveServiceControlInstance.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/RemoveServiceControlInstance.cs index 5ad750e9c6..64f482a01b 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/RemoveServiceControlInstance.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/RemoveServiceControlInstance.cs @@ -28,8 +28,7 @@ protected override void BeginProcessing() protected override void ProcessRecord() { var logger = new PSLogger(Host); - var zipfolder = ZipPath.Get(this); - var installer = new UnattendServiceControlInstaller(logger, zipfolder); + var installer = new UnattendServiceControlInstaller(logger); foreach (var name in Name) { diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/RemoveServiceControlRemote.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/RemoveServiceControlRemote.cs index 6ca8a34411..19309eacf3 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/RemoveServiceControlRemote.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/RemoveServiceControlRemote.cs @@ -22,8 +22,7 @@ protected override void ProcessRecord() { var logger = new PSLogger(Host); - var zipFolder = ZipPath.Get(this); - var installer = new UnattendServiceControlInstaller(logger, zipFolder); + var installer = new UnattendServiceControlInstaller(logger); var instance = InstanceFinder.FindInstanceByName(Name); if (instance == null) diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/ZipPath.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/ZipPath.cs deleted file mode 100644 index 6a3e0b4557..0000000000 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/ZipPath.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace ServiceControl.Management.PowerShell -{ - using System.IO; - using System.Management.Automation; - - class ZipPath - { - public static string Get(PSCmdlet instance) - { - return Path.Combine(Path.GetDirectoryName(instance.MyInvocation.MyCommand.Module.Path), ".."); - } - } -} \ No newline at end of file diff --git a/src/ServiceControl.Management.PowerShell/ServiceControl.Management.PowerShell.csproj b/src/ServiceControl.Management.PowerShell/ServiceControl.Management.PowerShell.csproj index 6636c8fc6a..a329136ed1 100644 --- a/src/ServiceControl.Management.PowerShell/ServiceControl.Management.PowerShell.csproj +++ b/src/ServiceControl.Management.PowerShell/ServiceControl.Management.PowerShell.csproj @@ -65,8 +65,4 @@ - - - -
diff --git a/src/ServiceControl.sln b/src/ServiceControl.sln index fd6b635bdf..7b93e36571 100644 --- a/src/ServiceControl.sln +++ b/src/ServiceControl.sln @@ -10,18 +10,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Installer", "Installer", "{ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceControlInstaller.Engine", "ServiceControlInstaller.Engine\ServiceControlInstaller.Engine.csproj", "{E2F41605-F664-4EC6-9D64-1F142CC3B7B2}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceControlInstaller.CustomActions", "ServiceControlInstaller.CustomActions\ServiceControlInstaller.CustomActions.csproj", "{2343779D-26D4-4F9D-A42F-32E146632EF0}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceControlInstaller.Packaging", "ServiceControlInstaller.Packaging\ServiceControlInstaller.Packaging.csproj", "{15F811B7-314C-4E9E-B5E9-35596045BBC4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceControl.Config", "ServiceControl.Config\ServiceControl.Config.csproj", "{C0EEF6D1-5DF7-4A26-9964-6376F465B085}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Setup", "Setup\Setup.csproj", "{F6473A7A-01B7-4A9A-B30C-4D200BFFFE4D}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceControlInstaller.Engine.UnitTests", "ServiceControlInstaller.Engine.UnitTests\ServiceControlInstaller.Engine.UnitTests.csproj", "{C7EC4072-F74C-410D-B0C3-4C05B7C9CFB7}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceControlInstaller.CustomActions.UnitTests", "ServiceControlInstaller.CustomActions.UnitTests\ServiceControlInstaller.CustomActions.UnitTests.csproj", "{20284C68-6381-4AE9-832C-F094E996D913}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ServiceControl.Transports", "ServiceControl.Transports", "{A21A1A89-0B07-4E87-8E3C-41D9C280DCB8}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LoadTests", "LoadTests", "{B05ACC74-412F-470C-872F-D1ABD75885B5}" @@ -43,6 +37,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9D957EE6-69D7-4680-AB54-9E8839C6BDBC}" ProjectSection(SolutionItems) = preProject ..\.github\workflows\ci.yml = ..\.github\workflows\ci.yml + ..\.github\workflows\release.yml = ..\.github\workflows\release.yml Custom.Build.props = Custom.Build.props Directory.Build.targets = Directory.Build.targets Directory.Packages.props = Directory.Packages.props @@ -221,18 +216,6 @@ Global {E2F41605-F664-4EC6-9D64-1F142CC3B7B2}.Release|x64.Build.0 = Release|Any CPU {E2F41605-F664-4EC6-9D64-1F142CC3B7B2}.Release|x86.ActiveCfg = Release|Any CPU {E2F41605-F664-4EC6-9D64-1F142CC3B7B2}.Release|x86.Build.0 = Release|Any CPU - {2343779D-26D4-4F9D-A42F-32E146632EF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2343779D-26D4-4F9D-A42F-32E146632EF0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2343779D-26D4-4F9D-A42F-32E146632EF0}.Debug|x64.ActiveCfg = Debug|Any CPU - {2343779D-26D4-4F9D-A42F-32E146632EF0}.Debug|x64.Build.0 = Debug|Any CPU - {2343779D-26D4-4F9D-A42F-32E146632EF0}.Debug|x86.ActiveCfg = Debug|Any CPU - {2343779D-26D4-4F9D-A42F-32E146632EF0}.Debug|x86.Build.0 = Debug|Any CPU - {2343779D-26D4-4F9D-A42F-32E146632EF0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2343779D-26D4-4F9D-A42F-32E146632EF0}.Release|Any CPU.Build.0 = Release|Any CPU - {2343779D-26D4-4F9D-A42F-32E146632EF0}.Release|x64.ActiveCfg = Release|Any CPU - {2343779D-26D4-4F9D-A42F-32E146632EF0}.Release|x64.Build.0 = Release|Any CPU - {2343779D-26D4-4F9D-A42F-32E146632EF0}.Release|x86.ActiveCfg = Release|Any CPU - {2343779D-26D4-4F9D-A42F-32E146632EF0}.Release|x86.Build.0 = Release|Any CPU {15F811B7-314C-4E9E-B5E9-35596045BBC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {15F811B7-314C-4E9E-B5E9-35596045BBC4}.Debug|Any CPU.Build.0 = Debug|Any CPU {15F811B7-314C-4E9E-B5E9-35596045BBC4}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -257,16 +240,6 @@ Global {C0EEF6D1-5DF7-4A26-9964-6376F465B085}.Release|x64.Build.0 = Release|Any CPU {C0EEF6D1-5DF7-4A26-9964-6376F465B085}.Release|x86.ActiveCfg = Release|Any CPU {C0EEF6D1-5DF7-4A26-9964-6376F465B085}.Release|x86.Build.0 = Release|Any CPU - {F6473A7A-01B7-4A9A-B30C-4D200BFFFE4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F6473A7A-01B7-4A9A-B30C-4D200BFFFE4D}.Debug|x64.ActiveCfg = Debug|Any CPU - {F6473A7A-01B7-4A9A-B30C-4D200BFFFE4D}.Debug|x64.Build.0 = Debug|Any CPU - {F6473A7A-01B7-4A9A-B30C-4D200BFFFE4D}.Debug|x86.ActiveCfg = Debug|Any CPU - {F6473A7A-01B7-4A9A-B30C-4D200BFFFE4D}.Debug|x86.Build.0 = Debug|Any CPU - {F6473A7A-01B7-4A9A-B30C-4D200BFFFE4D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F6473A7A-01B7-4A9A-B30C-4D200BFFFE4D}.Release|x64.ActiveCfg = Release|Any CPU - {F6473A7A-01B7-4A9A-B30C-4D200BFFFE4D}.Release|x64.Build.0 = Release|Any CPU - {F6473A7A-01B7-4A9A-B30C-4D200BFFFE4D}.Release|x86.ActiveCfg = Release|Any CPU - {F6473A7A-01B7-4A9A-B30C-4D200BFFFE4D}.Release|x86.Build.0 = Release|Any CPU {C7EC4072-F74C-410D-B0C3-4C05B7C9CFB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C7EC4072-F74C-410D-B0C3-4C05B7C9CFB7}.Debug|Any CPU.Build.0 = Debug|Any CPU {C7EC4072-F74C-410D-B0C3-4C05B7C9CFB7}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -279,18 +252,6 @@ Global {C7EC4072-F74C-410D-B0C3-4C05B7C9CFB7}.Release|x64.Build.0 = Release|Any CPU {C7EC4072-F74C-410D-B0C3-4C05B7C9CFB7}.Release|x86.ActiveCfg = Release|Any CPU {C7EC4072-F74C-410D-B0C3-4C05B7C9CFB7}.Release|x86.Build.0 = Release|Any CPU - {20284C68-6381-4AE9-832C-F094E996D913}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {20284C68-6381-4AE9-832C-F094E996D913}.Debug|Any CPU.Build.0 = Debug|Any CPU - {20284C68-6381-4AE9-832C-F094E996D913}.Debug|x64.ActiveCfg = Debug|Any CPU - {20284C68-6381-4AE9-832C-F094E996D913}.Debug|x64.Build.0 = Debug|Any CPU - {20284C68-6381-4AE9-832C-F094E996D913}.Debug|x86.ActiveCfg = Debug|Any CPU - {20284C68-6381-4AE9-832C-F094E996D913}.Debug|x86.Build.0 = Debug|Any CPU - {20284C68-6381-4AE9-832C-F094E996D913}.Release|Any CPU.ActiveCfg = Release|Any CPU - {20284C68-6381-4AE9-832C-F094E996D913}.Release|Any CPU.Build.0 = Release|Any CPU - {20284C68-6381-4AE9-832C-F094E996D913}.Release|x64.ActiveCfg = Release|Any CPU - {20284C68-6381-4AE9-832C-F094E996D913}.Release|x64.Build.0 = Release|Any CPU - {20284C68-6381-4AE9-832C-F094E996D913}.Release|x86.ActiveCfg = Release|Any CPU - {20284C68-6381-4AE9-832C-F094E996D913}.Release|x86.Build.0 = Release|Any CPU {AECE0265-F6E3-49A7-9F45-8847D359F05D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AECE0265-F6E3-49A7-9F45-8847D359F05D}.Debug|Any CPU.Build.0 = Debug|Any CPU {AECE0265-F6E3-49A7-9F45-8847D359F05D}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -981,12 +942,9 @@ Global {4F65FAE1-4C51-4BEF-956E-97C6A96807F9} = {350F72AB-142D-4AAD-9EF1-1A83DC991D87} {4C6B71B2-74EF-4B9E-88FF-C56532727C6D} = {CD6E567B-C17B-4C90-87CA-F6CB941EC307} {E2F41605-F664-4EC6-9D64-1F142CC3B7B2} = {55C388DD-2B39-4C2F-AEBD-AFD3444815F1} - {2343779D-26D4-4F9D-A42F-32E146632EF0} = {55C388DD-2B39-4C2F-AEBD-AFD3444815F1} {15F811B7-314C-4E9E-B5E9-35596045BBC4} = {55C388DD-2B39-4C2F-AEBD-AFD3444815F1} {C0EEF6D1-5DF7-4A26-9964-6376F465B085} = {55C388DD-2B39-4C2F-AEBD-AFD3444815F1} - {F6473A7A-01B7-4A9A-B30C-4D200BFFFE4D} = {55C388DD-2B39-4C2F-AEBD-AFD3444815F1} {C7EC4072-F74C-410D-B0C3-4C05B7C9CFB7} = {927A078A-E271-4878-A153-86D71AE510E2} - {20284C68-6381-4AE9-832C-F094E996D913} = {927A078A-E271-4878-A153-86D71AE510E2} {AECE0265-F6E3-49A7-9F45-8847D359F05D} = {B05ACC74-412F-470C-872F-D1ABD75885B5} {0A6BF34E-B7C3-4D3D-908F-BA74DAC4743F} = {B05ACC74-412F-470C-872F-D1ABD75885B5} {114E51C5-8EB1-44C6-AC5A-78C8EF6C7D61} = {A21A1A89-0B07-4E87-8E3C-41D9C280DCB8} diff --git a/src/ServiceControlInstaller.CustomActions.UnitTests/Logger.cs b/src/ServiceControlInstaller.CustomActions.UnitTests/Logger.cs deleted file mode 100644 index 7bd86d4d71..0000000000 --- a/src/ServiceControlInstaller.CustomActions.UnitTests/Logger.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace ServiceControlInstaller.CustomActions.UnitTests -{ - using System; - using Engine; - - public class TestLogger : ILogging - { - public void Info(string message) - { - Console.WriteLine("INFO : " + message); - } - - public void Warn(string message) - { - Console.WriteLine("WARN : " + message); - } - - public void Error(string message) - { - Console.WriteLine("ERROR : " + message); - } - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.CustomActions.UnitTests/RemoveOldVersionTests.cs b/src/ServiceControlInstaller.CustomActions.UnitTests/RemoveOldVersionTests.cs deleted file mode 100644 index ad8725ef15..0000000000 --- a/src/ServiceControlInstaller.CustomActions.UnitTests/RemoveOldVersionTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace ServiceControlInstaller.CustomActions.UnitTests -{ - using NUnit.Framework; - - [TestFixture] - public class RemoveOldVersionTests - { - [Test] - [Explicit] - public void RunCleanup() - { - CustomActionsMigrations.RemoveProductFromMSIList(new TestLogger()); - } - - [Test] - public void NoOp() - { - // dotnet test fails if no non-explicit tests are found in a test project - Assert.IsTrue(true); - } - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.CustomActions.UnitTests/ServiceControlInstaller.CustomActions.UnitTests.csproj b/src/ServiceControlInstaller.CustomActions.UnitTests/ServiceControlInstaller.CustomActions.UnitTests.csproj deleted file mode 100644 index 029b810d1c..0000000000 --- a/src/ServiceControlInstaller.CustomActions.UnitTests/ServiceControlInstaller.CustomActions.UnitTests.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - net472 - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/ServiceControlInstaller.CustomActions.UnitTests/TestsFilter.cs b/src/ServiceControlInstaller.CustomActions.UnitTests/TestsFilter.cs deleted file mode 100644 index cbb64ab1fc..0000000000 --- a/src/ServiceControlInstaller.CustomActions.UnitTests/TestsFilter.cs +++ /dev/null @@ -1 +0,0 @@ -[assembly: IncludeInDefaultTests()] \ No newline at end of file diff --git a/src/ServiceControlInstaller.CustomActions/.editorconfig b/src/ServiceControlInstaller.CustomActions/.editorconfig deleted file mode 100644 index e3bf403607..0000000000 --- a/src/ServiceControlInstaller.CustomActions/.editorconfig +++ /dev/null @@ -1,5 +0,0 @@ -[*.cs] - -# may be enabled in future -dotnet_diagnostic.PS0013.severity = none # A Func used as a method parameter with a Task, ValueTask, or ValueTask return type argument should have at least one CancellationToken parameter type argument unless it has a parameter type argument implementing ICancellableContext -dotnet_diagnostic.PS0018.severity = none # A task-returning method should have a CancellationToken parameter unless it has a parameter implementing ICancellableContext diff --git a/src/ServiceControlInstaller.CustomActions/CustomAction.config b/src/ServiceControlInstaller.CustomActions/CustomAction.config deleted file mode 100644 index 351b567c7f..0000000000 --- a/src/ServiceControlInstaller.CustomActions/CustomAction.config +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/ServiceControlInstaller.CustomActions/CustomActionsInstall.cs b/src/ServiceControlInstaller.CustomActions/CustomActionsInstall.cs deleted file mode 100644 index ac045697a9..0000000000 --- a/src/ServiceControlInstaller.CustomActions/CustomActionsInstall.cs +++ /dev/null @@ -1,209 +0,0 @@ -namespace ServiceControlInstaller.CustomActions -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading.Tasks; - using Engine.Configuration.ServiceControl; - using Engine.FileSystem; - using Engine.Instances; - using Engine.Unattended; - using ServiceControl.LicenseManagement; - using WixToolset.Dtf.WindowsInstaller; - - public class CustomActionsInstall - { - [CustomAction] - public static ActionResult ServiceControlUnattendedInstall(Session session) - { - var logger = new MSILogger(session); - - var unattendedInstaller = new UnattendServiceControlInstaller(logger, session["APPDIR"]); - var zipInfo = ServiceControlZipInfo.Find(session["APPDIR"] ?? "."); - - if (!zipInfo.Present) - { - logger.Error("Zip file not found. Service Control service instances can not be upgraded or installed"); - return ActionResult.Failure; - } - - UpgradeInstances(session, zipInfo, logger, unattendedInstaller); - UnattendedInstall(session, logger, unattendedInstaller).Wait(); - ImportLicenseInstall(session, logger); - return ActionResult.Success; - } - - static void UpgradeInstances(Session session, PlatformZipInfo zipInfo, MSILogger logger, UnattendServiceControlInstaller unattendedInstaller) - { - var options = new ServiceControlUpgradeOptions(); - - var upgradeInstancesPropertyValue = session["UPGRADEINSTANCES"]; - if (string.IsNullOrWhiteSpace(upgradeInstancesPropertyValue)) - { - return; - } - - upgradeInstancesPropertyValue = upgradeInstancesPropertyValue.Trim(); - - var forwardErrorMessagesPropertyValue = session["FORWARDERRORMESSAGES"]; - try - { - options.OverrideEnableErrorForwarding = bool.Parse(forwardErrorMessagesPropertyValue); - } - catch - { - options.OverrideEnableErrorForwarding = null; - } - - var auditRetentionPeriodPropertyValue = session["AUDITRETENTIONPERIOD"]; - try - { - options.AuditRetentionPeriod = TimeSpan.Parse(auditRetentionPeriodPropertyValue); - } - catch - { - options.AuditRetentionPeriod = null; - } - - var errorRetentionPeriodPropertyValue = session["ERRORRETENTIONPERIOD"]; - try - { - options.ErrorRetentionPeriod = TimeSpan.Parse(errorRetentionPeriodPropertyValue); - } - catch - { - options.ErrorRetentionPeriod = null; - } - - //determine what to upgrade - var instancesToUpgrade = new List(); - if (upgradeInstancesPropertyValue.Equals("*", StringComparison.OrdinalIgnoreCase) || upgradeInstancesPropertyValue.Equals("ALL", StringComparison.OrdinalIgnoreCase)) - { - instancesToUpgrade.AddRange(InstanceFinder.ServiceControlInstances()); - } - else - { - var candidates = upgradeInstancesPropertyValue.Replace(" ", string.Empty).Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); - instancesToUpgrade.AddRange(InstanceFinder.ServiceControlInstances().Where(instance => candidates.Contains(instance.Name, StringComparer.OrdinalIgnoreCase))); - } - - // do upgrades - foreach (var instance in instancesToUpgrade) - { - if (zipInfo.Version > instance.Version) - { - var upgradeInfo = UpgradeControl.GetUpgradeInfoForTargetVersion(zipInfo.Version, instance.Version); - - options.UpgradeInfo = upgradeInfo; - - if (!instance.AppConfig.AppSettingExists(ServiceControlSettings.ForwardErrorMessages.Name) & !options.OverrideEnableErrorForwarding.Value) - { - logger.Warn($"Unattend upgrade {instance.Name} to {zipInfo.Version} not attempted. FORWARDERRORMESSAGES MSI parameter was required because appsettings needed a value for '{ServiceControlSettings.ForwardErrorMessages.Name}'"); - continue; - } - - if (!options.AuditRetentionPeriod.HasValue) - { - if (!instance.AppConfig.AppSettingExists(ServiceControlSettings.AuditRetentionPeriod.Name)) - { - //Try migration first - if (instance.AppConfig.AppSettingExists(ServiceControlSettings.HoursToKeepMessagesBeforeExpiring.Name)) - { - var i = instance.AppConfig.Read(ServiceControlSettings.HoursToKeepMessagesBeforeExpiring.Name, -1); - if (i > 0) - { - options.AuditRetentionPeriod = TimeSpan.FromHours(i); - } - } - else - { - logger.Warn($"Unattend upgrade {instance.Name} to {zipInfo.Version} not attempted. AUDITRETENTIONPERIOD MSI parameter was required because appsettings needed a value for '{ServiceControlSettings.AuditRetentionPeriod.Name}'"); - continue; - } - } - } - - if (!instance.AppConfig.AppSettingExists(ServiceControlSettings.ErrorRetentionPeriod.Name) & !options.ErrorRetentionPeriod.HasValue) - { - logger.Warn($"Unattend upgrade {instance.Name} to {zipInfo.Version} not attempted. ERRORRETENTIONPERIOD MSI parameter was required because appsettings needed a value for '{ServiceControlSettings.ErrorRetentionPeriod.Name}'"); - continue; - } - - if (!unattendedInstaller.Upgrade(instance, options)) - { - logger.Warn($"Failed to upgrade {instance.Name} to {zipInfo.Version}"); - } - } - } - } - - static async Task UnattendedInstall(Session session, MSILogger logger, UnattendServiceControlInstaller unattendedInstaller) - { - logger.Info("Checking for unattended file"); - - var unattendedFilePropertyValue = session["UNATTENDEDFILE"]; - if (string.IsNullOrWhiteSpace(unattendedFilePropertyValue)) - { - return; - } - - var serviceAccount = session["SERVICEACCOUNT"]; - var password = session["PASSWORD"]; - logger.Info($"UNATTENDEDFILE: {unattendedFilePropertyValue}"); - var currentDirectory = session["CURRENTDIRECTORY"]; - var unattendedFilePath = Environment.ExpandEnvironmentVariables(Path.IsPathRooted(unattendedFilePropertyValue) ? unattendedFilePropertyValue : Path.Combine(currentDirectory, unattendedFilePropertyValue)); - - logger.Info($"Expanded unattended filepath to : {unattendedFilePropertyValue}"); - - if (File.Exists(unattendedFilePath)) - { - logger.Info($"File Exists : {unattendedFilePropertyValue}"); - var instanceToInstallDetails = ServiceControlNewInstance.Load(unattendedFilePath); - - if (!string.IsNullOrWhiteSpace(serviceAccount)) - { - instanceToInstallDetails.ServiceAccount = serviceAccount; - instanceToInstallDetails.ServiceAccountPwd = password; - } - - await unattendedInstaller.Add(instanceToInstallDetails, s => Task.FromResult(false)) - .ConfigureAwait(false); - } - else - { - logger.Error($"The specified unattended install file was not found : '{unattendedFilePath}'"); - } - } - - static void ImportLicenseInstall(Session session, MSILogger logger) - { - logger.Info("Checking for license file"); - - var licenseFilePropertyValue = session["LICENSEFILE"]; - if (string.IsNullOrWhiteSpace(licenseFilePropertyValue)) - { - return; - } - - logger.Info($"LICENSEFILE: {licenseFilePropertyValue}"); - var currentDirectory = session["CURRENTDIRECTORY"]; - var licenseFilePath = Environment.ExpandEnvironmentVariables(Path.IsPathRooted(licenseFilePropertyValue) ? licenseFilePropertyValue : Path.Combine(currentDirectory, licenseFilePropertyValue)); - - logger.Info($"Expanded license filepath to : {licenseFilePropertyValue}"); - - if (File.Exists(licenseFilePath)) - { - logger.Info($"File Exists : {licenseFilePropertyValue}"); - if (!LicenseManager.TryImportLicense(licenseFilePath, out var errormessage)) - { - logger.Error(errormessage); - } - } - else - { - logger.Error($"The specified license install file was not found : '{licenseFilePath}'"); - } - } - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.CustomActions/CustomActionsMigration.cs b/src/ServiceControlInstaller.CustomActions/CustomActionsMigration.cs deleted file mode 100644 index b84091750d..0000000000 --- a/src/ServiceControlInstaller.CustomActions/CustomActionsMigration.cs +++ /dev/null @@ -1,172 +0,0 @@ -namespace ServiceControlInstaller.CustomActions -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Engine; - using Extensions; - using Microsoft.Win32; - using WixToolset.Dtf.WindowsInstaller; - - public class CustomActionsMigrations - { - [CustomAction] - public static ActionResult ServiceControlMigration(Session session) - { - var logger = new MSILogger(session); - RemoveProductFromMSIList(logger); - RemoveOrphanedInstallationKeys(); - return ActionResult.Success; - } - - public static void RemoveProductFromMSIList(ILogging logger) - { - var upgradeKeyPath = $@"SOFTWARE\Classes\Installer\UpgradeCodes\{UpgradeCode.Flip():N}"; - using (var installerKey = Registry.LocalMachine.OpenSubKey(upgradeKeyPath, true)) - { - if (installerKey == null) - { - return; - } - - logger.Info($"Found upgrade code for old version {UpgradeCode:B}"); - foreach (var flippedProductCodeString in installerKey.GetValueNames()) - { - if (!Guid.TryParse(flippedProductCodeString, out var flippedProductCode)) - { - continue; - } - - var keysToDelete = new[] - { - $@"SOFTWARE\Classes\Installer\Products\{flippedProductCode:N}", - $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\{flippedProductCode:N}" - }; - foreach (var regkey in keysToDelete) - { - bool keyExists; - using (var productKey = Registry.LocalMachine.OpenSubKey(regkey)) - { - keyExists = productKey != null; - } - - if (keyExists) - { - logger.Info($@"Removing HKEY_LOCAL_MACHINE\{regkey}"); - Registry.LocalMachine.DeleteSubKeyTree(regkey, false); - } - } - - var productCode = flippedProductCode.Flip().ToString("B"); - - var uninstallKey = $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{productCode}"; - using (var wowRegistryRoot = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)) - { - var productKeyPresent = false; - using (var productKey = wowRegistryRoot.OpenSubKey(uninstallKey)) - { - if (productKey != null) - { - productKeyPresent = true; - var displayName = (string)productKey.GetValue("DisplayName", string.Empty); - var displayVersion = (string)productKey.GetValue("DisplayVersion", string.Empty); - - var descriptiveVersion = $"{displayName} {displayVersion}"; - - if (!string.IsNullOrWhiteSpace(descriptiveVersion)) - { - var descriptiveUninstallKeyPath = $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{descriptiveVersion}"; - bool descriptiveUninstallPresent; - using (var descriptiveUninstallkey = wowRegistryRoot.OpenSubKey(descriptiveUninstallKeyPath)) - { - descriptiveUninstallPresent = descriptiveUninstallkey != null; - } - - if (descriptiveUninstallPresent) - { - if (Environment.Is64BitOperatingSystem) - { - logger.Info($@"Removing HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{descriptiveVersion}"); - } - else - { - logger.Info($@"Removing HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{descriptiveVersion}"); - } - - logger.Info($@"Removing HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{descriptiveVersion}"); - wowRegistryRoot.DeleteSubKeyTree(descriptiveUninstallKeyPath); - } - } - } - } - - if (productKeyPresent) - { - if (Environment.Is64BitOperatingSystem) - { - logger.Info($@"Removing HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{productCode}"); - } - else - { - logger.Info($@"Removing HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{productCode}"); - } - - Registry.LocalMachine.DeleteSubKeyTree(upgradeKeyPath, false); - } - } - } - } - - logger.Info($@"Removing HKEY_LOCAL_MACHINE\{upgradeKeyPath}"); - Registry.LocalMachine.DeleteSubKeyTree(upgradeKeyPath, false); - } - - static void RemoveOrphanedInstallationKeys() - { - using (var wowRegistryRoot = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)) - { - foreach (var uninstallKey in oldProducts.Select(product => $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{product.Value}")) - { - wowRegistryRoot.DeleteSubKeyTree(uninstallKey, false); - } - } - - foreach (var uninstallKey in oldProducts.Select(product => $@"SOFTWARE\Classes\Installer\Products\{new Guid(product.Value).Flip():N}")) - { - Registry.LocalMachine.DeleteSubKey(uninstallKey, false); - } - } - - static readonly Guid UpgradeCode = new Guid("{8F59B8C7-A3EB-4D19-9B88-0EA69FF1B26C}"); - - // This list does not need to be updated beyond version 1.6.3 - // After 1.6.3 a new MSI product code was used. This was done at the introduction of the management app. - // The older versions of the MSI handled install and uninstall of a single service, if we'd used the same MSI product code then the - // installation of the management app would have also uninstalled existing ServiceControl instances. - // Instead this custom action removes the registry entries that indicate the old MSI was installed. - // Add/Remove/Upgrade of the services is all done by the app now. - static readonly Dictionary oldProducts = new Dictionary - { - {"1.0.0", "{12BDB3E7-3A70-410A-A2C0-43037E33E3E6}"}, - {"1.1.0", "{284D3F83-82F3-4A17-A8AA-F2CBF0298152}"}, - {"1.2.0", "{988C86F3-B5CB-4F94-BD38-D8B62C7C998F}"}, - {"1.2.1", "{1BD45935-BB9D-464F-AC09-A824ED476BFA}"}, - {"1.2.2", "{6AF38546-DA28-4477-9A33-0B6FC759FB69}"}, - {"1.2.3", "{AC9365C3-7EF5-4CF5-A356-B5288CBF425F}"}, - {"1.3.0", "{7854D33C-307C-477A-850F-6D99AEBA4EF6}"}, - {"1.4.0", "{404D7A44-2767-4396-9C44-48738FAA59F1}"}, - {"1.4.1", "{BDEDFBEA-9FAB-488E-ACE3-939747E9B609}"}, - {"1.4.2", "{E18AF9FC-F4BE-4713-B6DC-A14A05BADA59}"}, - {"1.4.3", "{EF682A8F-9224-44DD-949F-D910D12A8C53}"}, - {"1.4.4", "{D23AF353-1F0E-4C0E-909A-166E500B9715}"}, - {"1.5.0", "{A4B5B1FE-8A11-4920-8741-3CA68F0A0136}"}, - {"1.5.1", "{78E3CB24-D00A-4CC6-A7C2-CDB66231B8DA}"}, - {"1.5.2", "{3AC40768-536F-4E2F-A2A4-E198E6FBA1E9}"}, - {"1.5.3", "{8EFE762B-399A-4EB7-9C5E-AAB59EB73F98}"}, - {"1.6.0", "{3260B961-C140-4BFC-9A54-583274405F43}"}, - {"1.6.1", "{FD225DAC-FE16-4634-9C80-C1F3533347B6}"}, - {"1.6.2", "{164571BF-F42F-4492-9047-25101B0ABB87}"}, - {"1.6.3", "{657CB451-3B6E-4266-90DF-F6C16A8AC2CE}"} - }; - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.CustomActions/CustomActionsPowerShell.cs b/src/ServiceControlInstaller.CustomActions/CustomActionsPowerShell.cs deleted file mode 100644 index 6ca0d28935..0000000000 --- a/src/ServiceControlInstaller.CustomActions/CustomActionsPowerShell.cs +++ /dev/null @@ -1,85 +0,0 @@ -namespace ServiceControlInstaller.CustomActions -{ - using System; - using System.Linq; - using System.Runtime.InteropServices; - using WixToolset.Dtf.WindowsInstaller; - - public class CustomActionsPowerShell - { - const string PSModulePath = "PSMODULEPATH"; - - [DllImport("User32.DLL")] - static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam); - - [CustomAction] - public static ActionResult AddToPSModuleEnvironmentVar(Session session) - { - //Advanced Installer doesn't notify of environment changes on system environment variables - var appDir = session["MODULEDIR"]; - if (appDir.EndsWith(@"\")) - { - appDir = appDir.Remove(appDir.Length - 1); - } - - var environmentVariable = Environment.GetEnvironmentVariable(PSModulePath, EnvironmentVariableTarget.Machine); - if (environmentVariable != null) - { - var parts = environmentVariable.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList(); - if (!parts.Any(p => p.Equals(appDir, StringComparison.OrdinalIgnoreCase))) - { - parts.Add(appDir); - var newValue = string.Join(";", parts); - Environment.SetEnvironmentVariable(PSModulePath, newValue, EnvironmentVariableTarget.Machine); - Log(session, "Updated PowerShell module path at " + appDir); - } - } - return ActionResult.Success; - } - - [CustomAction] - public static ActionResult RemoveFromPSModuleEnvironmentVar(Session session) - { - //Advanced Installer doesn't notify of environment changes on system environment variables - var appDir = session["MODULEDIR"]; - - var environmentVariable = Environment.GetEnvironmentVariable(PSModulePath, EnvironmentVariableTarget.Machine); - if (environmentVariable != null) - { - var parts = environmentVariable.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList(); - if (parts.Any(p => p.Equals(appDir, StringComparison.OrdinalIgnoreCase))) - { - var newParts = parts.Where(p => !p.Equals(appDir, StringComparison.OrdinalIgnoreCase)).ToList(); - var newValue = string.Join(";", newParts); - Environment.SetEnvironmentVariable(PSModulePath, newValue, EnvironmentVariableTarget.Machine); - Log(session, "Removed PowerShell module path."); - } - } - return ActionResult.Success; - } - - static void Log(Session session, string message, params object[] args) - { - LogAction(session, string.Format(message, args)); - } - - public static Action LogAction = (s, m) => s.Log(m); - - public static Func GetAction = (s, key) => s[key]; - - public static Action SetAction = (s, key, value) => s[key] = value; - } - - public static class SessionExtensions - { - public static string Get(this Session session, string key) - { - return CustomActionsPowerShell.GetAction(session, key); - } - - public static void Set(this Session session, string key, string value) - { - CustomActionsPowerShell.SetAction(session, key, value); - } - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.CustomActions/CustomActionsUninstall.cs b/src/ServiceControlInstaller.CustomActions/CustomActionsUninstall.cs deleted file mode 100644 index 5bd1bdc036..0000000000 --- a/src/ServiceControlInstaller.CustomActions/CustomActionsUninstall.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace ServiceControlInstaller.CustomActions -{ - using System; - using Engine.Instances; - using Engine.Unattended; - using WixToolset.Dtf.WindowsInstaller; - - public class CustomActionsUninstall - { - [CustomAction] - public static ActionResult ServiceControlInstanceCount(Session session) - { - var instanceCount = InstanceFinder.ServiceControlInstances().Count + - InstanceFinder.MonitoringInstances().Count; - session["SCINSTANCECOUNT"] = instanceCount.ToString(); - return ActionResult.Success; - } - - [CustomAction] - public static ActionResult ServiceControlUnattendedRemoval(Session session) - { - var logger = new MSILogger(session); - var removeInstancesPropertyValue = session["REMOVEALLINSTANCESANDDATA"]; - if (string.IsNullOrWhiteSpace(removeInstancesPropertyValue)) - { - return ActionResult.NotExecuted; - } - - switch (removeInstancesPropertyValue.ToUpper()) - { - case "YES": - case "TRUE": - break; - default: - return ActionResult.NotExecuted; - } - - if (InstanceFinder.ServiceControlInstances().Count == 0) - { - return ActionResult.Success; - } - - var unattendedInstaller = new UnattendServiceControlInstaller(logger, session["APPDIR"]); - foreach (var instance in InstanceFinder.ServiceControlInstances()) - { - try - { - unattendedInstaller.Delete(instance.Name, true, true); - } - catch (Exception ex) - { - logger.Error($"Error thrown when removing instance {instance.Name} - {ex}"); - } - } - - return ActionResult.Success; - } - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.CustomActions/Extensions/GuidExtensions.cs b/src/ServiceControlInstaller.CustomActions/Extensions/GuidExtensions.cs deleted file mode 100644 index 49cf746980..0000000000 --- a/src/ServiceControlInstaller.CustomActions/Extensions/GuidExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace ServiceControlInstaller.CustomActions.Extensions -{ - using System; - using System.Linq; - using System.Text; - - static class GuidExtensions - { - /// - /// For some reason MS converts MSI product code and upgrade code GUIDs when using them in the registry. - /// For instance a product code "{12BDB3E7-3A70-410A-A2C0-43037E33E3E6}" is represented as 7E3BDB2107A3A0142A0C3430E7333E6E - /// in the registry key: - /// `HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Products\7E3BDB2107A3A0142A0C3430E7333E6E` - /// This flip method will convert the GUID which can then be string formatted using "N" to get registry format. - /// The algorithm is reversible, so if you flip the GUID again your back to where you started. - /// - /// Guid to Flip - /// Flipped Guid - public static Guid Flip(this Guid guid) - { - var sb = new StringBuilder(); - var parts = guid.ToString("D").Split('-'); - foreach (var part in parts.Take(3)) - { - sb.Append(part.Reverse()); - } - - foreach (var chunk in parts.Skip(3).SelectMany(part => part.Chunks(2))) - { - sb.Append(chunk.Reverse()); - } - - return new Guid(sb.ToString()); - } - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.CustomActions/Extensions/StringExtensions.cs b/src/ServiceControlInstaller.CustomActions/Extensions/StringExtensions.cs deleted file mode 100644 index c834e9185c..0000000000 --- a/src/ServiceControlInstaller.CustomActions/Extensions/StringExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace ServiceControlInstaller.CustomActions.Extensions -{ - using System.Collections.Generic; - using System.Linq; - - static class StringExtensions - { - public static string Reverse(this string input) - { - return new string(input.ToCharArray().Reverse().ToArray()); - } - - public static IEnumerable Chunks(this string str, int chunkSize) - { - return Enumerable.Range(0, str.Length / chunkSize) - .Select(i => str.Substring(i * chunkSize, chunkSize)); - } - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.CustomActions/InternalsVisibleTo.cs b/src/ServiceControlInstaller.CustomActions/InternalsVisibleTo.cs deleted file mode 100644 index b6f80fc61d..0000000000 --- a/src/ServiceControlInstaller.CustomActions/InternalsVisibleTo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("ServiceControlInstaller.CustomActions.UnitTests")] diff --git a/src/ServiceControlInstaller.CustomActions/MSILogger.cs b/src/ServiceControlInstaller.CustomActions/MSILogger.cs deleted file mode 100644 index cf42a1b013..0000000000 --- a/src/ServiceControlInstaller.CustomActions/MSILogger.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace ServiceControlInstaller.CustomActions -{ - using Engine; - using WixToolset.Dtf.WindowsInstaller; - - public class MSILogger : ILogging - { - public MSILogger(Session session) - { - this.session = session; - } - - public void Info(string message) - { - session.Log(message); - } - - public void Warn(string message) - { - session.Log("WARN: {0}", message); - } - - public void Error(string message) - { - session.Log("ERROR: {0}", message); - } - - Session session; - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.CustomActions/ServiceControlInstaller.CustomActions.csproj b/src/ServiceControlInstaller.CustomActions/ServiceControlInstaller.CustomActions.csproj deleted file mode 100644 index bf1a6ce5c1..0000000000 --- a/src/ServiceControlInstaller.CustomActions/ServiceControlInstaller.CustomActions.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - net472 - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/PersistenceManifestTests.ApproveAuditInstanceManifests.approved.txt b/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/PersistenceManifestTests.ApproveAuditInstanceManifests.approved.txt new file mode 100644 index 0000000000..c72a8fb834 --- /dev/null +++ b/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/PersistenceManifestTests.ApproveAuditInstanceManifests.approved.txt @@ -0,0 +1,5 @@ +[ + "InMemory: In-memory", + "RavenDB35: RavenDB 3.5", + "RavenDB5: RavenDB 5" +] \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/PersistenceManifestTests.ApprovePrimaryInstanceManifests.approved.txt b/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/PersistenceManifestTests.ApprovePrimaryInstanceManifests.approved.txt new file mode 100644 index 0000000000..a6bd465c16 --- /dev/null +++ b/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/PersistenceManifestTests.ApprovePrimaryInstanceManifests.approved.txt @@ -0,0 +1,4 @@ +[ + "RavenDB35: RavenDB 3.5", + "RavenDB5: RavenDB 5" +] \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine.UnitTests/Configuration/AuditInstanceTests.cs b/src/ServiceControlInstaller.Engine.UnitTests/Configuration/AuditInstanceTests.cs index 354eb9a77e..75b8a09585 100644 --- a/src/ServiceControlInstaller.Engine.UnitTests/Configuration/AuditInstanceTests.cs +++ b/src/ServiceControlInstaller.Engine.UnitTests/Configuration/AuditInstanceTests.cs @@ -13,10 +13,12 @@ [TestFixture] class AuditInstanceTests : InstallationFixture { + const string zipResourceName = "Particular.ServiceControl.Audit.zip"; + [Test] public void Should_default_to_raven35_when_no_config_entry_exists() { - var newInstance = ServiceControlAuditNewInstance.CreateWithPersistence(ZipFileFolder.FullName, "RavenDB35"); + var newInstance = ServiceControlAuditNewInstance.CreateWithPersistence("RavenDB35"); newInstance.InstallPath = InstallPath; newInstance.TransportPackage = ServiceControlCoreTransports.All.Single(t => t.Name == TransportNames.MSMQ); @@ -25,7 +27,7 @@ public void Should_default_to_raven35_when_no_config_entry_exists() newInstance.HostName = "localhost"; newInstance.DatabaseMaintenancePort = 33333; - newInstance.CopyFiles(ZipFilePath); + newInstance.CopyFiles(zipResourceName); newInstance.WriteConfigurationFile(); //delete the setting to simulate an existing older instance @@ -36,11 +38,11 @@ public void Should_default_to_raven35_when_no_config_entry_exists() entry.ParentNode.RemoveChild(entry); existingConfigFile.Save(configPath); - var instance = new ServiceControlAuditInstance(new FakeWindowsServiceController(Path.Combine(InstallPath, "ServiceControl.Audit.exe")), ZipFileFolder.FullName); + var instance = new ServiceControlAuditInstance(new FakeWindowsServiceController(Path.Combine(InstallPath, "ServiceControl.Audit.exe"))); instance.Reload(); - instance.UpgradeFiles(ZipFilePath); + instance.UpgradeFiles(zipResourceName); FileAssert.DoesNotExist(Path.Combine(InstallPath, "ServiceControl.Audit.Persistence.RavenDb.dll")); FileAssert.DoesNotExist(Path.Combine(InstallPath, "ServiceControl.Audit.Persistence.RavenDb5.dll")); @@ -52,7 +54,7 @@ public void Should_default_to_raven35_when_no_config_entry_exists() [Test] public void Should_update_existing_persister() { - var newInstance = ServiceControlAuditNewInstance.CreateWithPersistence(ZipFileFolder.FullName, "RavenDB5"); + var newInstance = ServiceControlAuditNewInstance.CreateWithPersistence("RavenDB5"); newInstance.InstallPath = InstallPath; newInstance.TransportPackage = ServiceControlCoreTransports.All.Single(t => t.Name == TransportNames.MSMQ); @@ -62,10 +64,10 @@ public void Should_update_existing_persister() newInstance.HostName = "localhost"; newInstance.DatabaseMaintenancePort = 33333; - newInstance.CopyFiles(ZipFilePath); + newInstance.CopyFiles(zipResourceName); newInstance.WriteConfigurationFile(); - var instance = new ServiceControlAuditInstance(new FakeWindowsServiceController(Path.Combine(InstallPath, "ServiceControl.Audit.exe")), ZipFileFolder.FullName); + var instance = new ServiceControlAuditInstance(new FakeWindowsServiceController(Path.Combine(InstallPath, "ServiceControl.Audit.exe"))); instance.Reload(); @@ -74,7 +76,7 @@ public void Should_update_existing_persister() //delete the persitence dll to make sure it gets re-installed File.Delete(persisterFilePath); - instance.UpgradeFiles(ZipFilePath); + instance.UpgradeFiles(zipResourceName); FileAssert.Exists(persisterFilePath); } @@ -82,7 +84,7 @@ public void Should_update_existing_persister() public void Should_remove_log_and_db_folders_on_uninstall() { - var newInstance = ServiceControlAuditNewInstance.CreateWithPersistence(ZipFileFolder.FullName, "RavenDB35"); + var newInstance = ServiceControlAuditNewInstance.CreateWithPersistence("RavenDB35"); newInstance.InstallPath = InstallPath; newInstance.TransportPackage = ServiceControlCoreTransports.All.Single(t => t.Name == TransportNames.MSMQ); @@ -91,7 +93,7 @@ public void Should_remove_log_and_db_folders_on_uninstall() newInstance.HostName = "localhost"; newInstance.DatabaseMaintenancePort = 33333; - newInstance.CopyFiles(ZipFilePath); + newInstance.CopyFiles(zipResourceName); newInstance.WriteConfigurationFile(); var fakeRavenLogsPath = Path.Combine(InstallPath, "RavenLogs"); @@ -101,7 +103,7 @@ public void Should_remove_log_and_db_folders_on_uninstall() DefaultValue = fakeRavenLogsPath }); - var instance = new ServiceControlAuditInstance(new FakeWindowsServiceController(Path.Combine(InstallPath, "ServiceControl.Audit.exe")), ZipFileFolder.FullName); + var instance = new ServiceControlAuditInstance(new FakeWindowsServiceController(Path.Combine(InstallPath, "ServiceControl.Audit.exe"))); instance.Reload(); diff --git a/src/ServiceControlInstaller.Engine.UnitTests/Configuration/InstallationFixture.cs b/src/ServiceControlInstaller.Engine.UnitTests/Configuration/InstallationFixture.cs index c88d4d2dd2..1ca584aaab 100644 --- a/src/ServiceControlInstaller.Engine.UnitTests/Configuration/InstallationFixture.cs +++ b/src/ServiceControlInstaller.Engine.UnitTests/Configuration/InstallationFixture.cs @@ -1,8 +1,6 @@ namespace ServiceControlInstaller.Engine.UnitTests.Configuration { - using System; using System.IO; - using System.Linq; using NUnit.Framework; public class InstallationFixture @@ -10,8 +8,6 @@ public class InstallationFixture [SetUp] public void SetUp() { - ZipFileFolder = GetZipFolder(); - testPath = Path.Combine(Path.GetTempPath(), TestContext.CurrentContext.Test.ID); if (Directory.Exists(testPath)) @@ -22,10 +18,6 @@ public void SetUp() InstallPath = Path.Combine(testPath, "install"); DbPath = Path.Combine(testPath, "db"); LogPath = Path.Combine(testPath, "log"); - - ZipFilePath = ZipFileFolder.EnumerateFiles("*.zip") - .Single(f => f.Name.Contains(".Audit")) - .FullName; } [TearDown] @@ -37,36 +29,12 @@ public void TearDown() } } - static DirectoryInfo GetZipFolder() - { - var currentFolder = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); - - while (currentFolder != null) - { - var file = currentFolder.EnumerateFiles("*.sln", SearchOption.TopDirectoryOnly) - .SingleOrDefault(); - - if (file != null) - { - return new DirectoryInfo(Path.Combine(file.Directory.Parent.FullName, "zip")); - } - - currentFolder = currentFolder.Parent; - } - - throw new Exception("Cannot find zip folder"); - } - - protected DirectoryInfo ZipFileFolder; - protected string InstallPath; protected string DbPath; protected string LogPath; - protected string ZipFilePath; - string testPath; } } diff --git a/src/ServiceControlInstaller.Engine.UnitTests/Configuration/NewAuditInstanceTests.cs b/src/ServiceControlInstaller.Engine.UnitTests/Configuration/NewAuditInstanceTests.cs index c384906100..9d2a28a2dc 100644 --- a/src/ServiceControlInstaller.Engine.UnitTests/Configuration/NewAuditInstanceTests.cs +++ b/src/ServiceControlInstaller.Engine.UnitTests/Configuration/NewAuditInstanceTests.cs @@ -9,10 +9,12 @@ [TestFixture] class NewAuditInstanceTests : InstallationFixture { + const string zipResourceName = "Particular.ServiceControl.Audit.zip"; + [Test] public void Should_install_raven5_for_new_instances() { - var newInstance = ServiceControlAuditNewInstance.CreateWithDefaultPersistence(ZipFileFolder.FullName); + var newInstance = ServiceControlAuditNewInstance.CreateWithDefaultPersistence(); newInstance.InstallPath = InstallPath; newInstance.TransportPackage = ServiceControlCoreTransports.All.Single(t => t.Name == TransportNames.MSMQ); @@ -22,7 +24,7 @@ public void Should_install_raven5_for_new_instances() newInstance.HostName = "localhost"; newInstance.DatabaseMaintenancePort = 33333; - newInstance.CopyFiles(ZipFilePath); + newInstance.CopyFiles(zipResourceName); newInstance.WriteConfigurationFile(); FileAssert.Exists(Path.Combine(InstallPath, "ServiceControl.Audit.Persistence.RavenDb5.dll")); @@ -30,17 +32,5 @@ public void Should_install_raven5_for_new_instances() Approver.Verify(configFile, input => input.Replace(DbPath, "value-not-asserted").Replace(LogPath, "value-not-asserted")); } - - [Test] - public void Should_parse_all_persister_manifests() - { - var zipFiles = ZipFileFolder.EnumerateFiles(); - - var zipFile = zipFiles.Single(f => f.Name.StartsWith("Particular.ServiceControl.Audit")); - - var allManifests = ServiceControlPersisters.LoadAllManifests(zipFile.FullName); - - CollectionAssert.IsNotEmpty(allManifests); - } } } diff --git a/src/ServiceControlInstaller.Engine.UnitTests/Configuration/PersistenceManifestTests.cs b/src/ServiceControlInstaller.Engine.UnitTests/Configuration/PersistenceManifestTests.cs new file mode 100644 index 0000000000..4f303dcbd3 --- /dev/null +++ b/src/ServiceControlInstaller.Engine.UnitTests/Configuration/PersistenceManifestTests.cs @@ -0,0 +1,27 @@ +namespace ServiceControlInstaller.Engine.UnitTests.Configuration +{ + using System.Linq; + using NUnit.Framework; + using Particular.Approvals; + using ServiceControlInstaller.Engine.Instances; + + public class PersistenceManifestTests + { + /* + * Persistence methods that are no longer supported still need to ship a persistence.manifest file so that + * ServiceControl Management can still display information about older ServiceControl instances. + */ + + [Test] + public void ApprovePrimaryInstanceManifests() + { + Approver.Verify(ServiceControlPersisters.PrimaryPersistenceManifests.Select(m => $"{m.Name}: {m.DisplayName}")); + } + + [Test] + public void ApproveAuditInstanceManifests() + { + Approver.Verify(ServiceControlPersisters.AuditPersistenceManifests.Select(m => $"{m.Name}: {m.DisplayName}")); + } + } +} diff --git a/src/ServiceControlInstaller.Engine.UnitTests/RunEngineTasksExplicitly.cs b/src/ServiceControlInstaller.Engine.UnitTests/RunEngineTasksExplicitly.cs index 56f972e49e..51e610c160 100644 --- a/src/ServiceControlInstaller.Engine.UnitTests/RunEngineTasksExplicitly.cs +++ b/src/ServiceControlInstaller.Engine.UnitTests/RunEngineTasksExplicitly.cs @@ -19,7 +19,7 @@ public class RunEngine [Explicit] public void DeleteInstance() { - var installer = new UnattendServiceControlInstaller(new TestLogger(), DeploymentCache); + var installer = new UnattendServiceControlInstaller(new TestLogger()); foreach (var instance in InstanceFinder.ServiceControlInstances().Where(p => p.Name.StartsWith("Test.ServiceControl", StringComparison.OrdinalIgnoreCase))) { installer.Delete(instance.Name, true, true); @@ -30,7 +30,7 @@ public void DeleteInstance() [Explicit] public void UpgradeInstance() { - var installer = new UnattendServiceControlInstaller(new TestLogger(), DeploymentCache); + var installer = new UnattendServiceControlInstaller(new TestLogger()); foreach (var instance in InstanceFinder.ServiceControlInstances().Where(p => p.Name.StartsWith("Test.ServiceControl", StringComparison.OrdinalIgnoreCase))) { installer.Upgrade(instance, new ServiceControlUpgradeOptions @@ -46,10 +46,10 @@ public void UpgradeInstance() [Explicit] public async Task CreateInstanceMSMQ() { - var installer = new UnattendServiceControlInstaller(new TestLogger(), DeploymentCache); + var installer = new UnattendServiceControlInstaller(new TestLogger()); var instanceName = "Test.ServiceControl.Msmq"; var root = Path.Combine(Path.GetTempPath(), instanceName); - var details = ServiceControlNewInstance.CreateWithDefaultPersistence(DeploymentCache); + var details = ServiceControlNewInstance.CreateWithDefaultPersistence(); details.DisplayName = instanceName.Replace(".", " "); details.Name = instanceName; @@ -87,7 +87,7 @@ public async Task CreateInstanceMSMQ() public async Task ChangeConfigTests() { var logger = new TestLogger(); - var installer = new UnattendServiceControlInstaller(logger, DeploymentCache); + var installer = new UnattendServiceControlInstaller(logger); logger.Info("Deleting instances"); DeleteInstance(); @@ -138,7 +138,5 @@ void RemoveAltMSMQQueues() } } } - - const string DeploymentCache = @"..\..\..\..\..\Zip"; } } \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine.UnitTests/Validation/QueueValidationTests.cs b/src/ServiceControlInstaller.Engine.UnitTests/Validation/QueueValidationTests.cs index 4b175e972f..d6b8b8df1c 100644 --- a/src/ServiceControlInstaller.Engine.UnitTests/Validation/QueueValidationTests.cs +++ b/src/ServiceControlInstaller.Engine.UnitTests/Validation/QueueValidationTests.cs @@ -2,7 +2,6 @@ namespace ServiceControlInstaller.Engine.UnitTests.Validation { using System; using System.Collections.Generic; - using System.IO; using System.Linq; using Engine.Validation; using Instances; @@ -142,7 +141,7 @@ public void Init() [Test] public void CheckQueueNamesAreUniqueShouldSucceed() { - var newInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(GetZipFolder().FullName); + var newInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(); newInstance.TransportPackage = ServiceControlCoreTransports.All.First(t => t.Name == TransportNames.MSMQ); newInstance.ErrorLogQueue = "errorlog"; @@ -164,7 +163,7 @@ public void CheckChainingOfAuditQueues_ShouldSucceed() AuditQueue = @"audit" }; - var newInstance = ServiceControlAuditNewInstance.CreateWithDefaultPersistence(GetZipFolder().FullName); + var newInstance = ServiceControlAuditNewInstance.CreateWithDefaultPersistence(); newInstance.TransportPackage = ServiceControlCoreTransports.All.First(t => t.Name == TransportNames.MSMQ); newInstance.AuditQueue = "audit"; @@ -180,7 +179,7 @@ public void CheckChainingOfAuditQueues_ShouldSucceed() [Test] public void CheckQueueNamesAreUniqueShouldThrow() { - var newInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(GetZipFolder().FullName); + var newInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(); newInstance.TransportPackage = ServiceControlCoreTransports.All.First(t => t.Name == TransportNames.MSMQ); newInstance.ErrorLogQueue = "error"; @@ -199,7 +198,7 @@ public void CheckQueueNamesAreUniqueShouldThrow() [Test] public void CheckQueueNamesAreNotTakenByAnotherInstance_ShouldSucceed() { - var newInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(GetZipFolder().FullName); + var newInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(); newInstance.TransportPackage = ServiceControlCoreTransports.All.First(t => t.Name == TransportNames.MSMQ); newInstance.ErrorLogQueue = "errorlog2"; @@ -216,7 +215,7 @@ public void CheckQueueNamesAreNotTakenByAnotherInstance_ShouldSucceed() public void CheckQueueNamesAreNotTakenByAnotherInstance_ShouldThrow() { var expectedError = "Some queue names specified are already assigned to another ServiceControl instance - Correct the values for ErrorLogQueue, ErrorQueue"; - var newInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(GetZipFolder().FullName); + var newInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(); { newInstance.TransportPackage = ServiceControlCoreTransports.All.First(t => t.Name == TransportNames.MSMQ); newInstance.ErrorLogQueue = "errorlog"; @@ -234,7 +233,7 @@ public void CheckQueueNamesAreNotTakenByAnotherInstance_ShouldThrow() expectedError = "The queue name for ErrorQueue is already assigned to another ServiceControl instance"; // with default names - var defaultInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(GetZipFolder().FullName); + var defaultInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(); defaultInstance.ErrorQueue = "Error"; p = new QueueNameValidator(defaultInstance) @@ -251,7 +250,7 @@ public void DuplicateQueueNamesAreAllowedOnDifferentTransports_ShouldNotThrow() { var expectedError = "Some queue names specified are already assigned to another ServiceControl instance - Correct the values for ErrorLogQueue, ErrorQueue"; - var newInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(GetZipFolder().FullName); + var newInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(); newInstance.TransportPackage = ServiceControlCoreTransports.All.First(t => t.Name == TransportNames.RabbitMQQuorumConventionalRoutingTopology); newInstance.ErrorLogQueue = "errorlog"; @@ -268,7 +267,7 @@ public void DuplicateQueueNamesAreAllowedOnDifferentTransports_ShouldNotThrow() expectedError = "The queue name for ErrorQueue is already assigned to another ServiceControl instance"; // with default names - var defaultInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(GetZipFolder().FullName); + var defaultInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(); defaultInstance.ErrorQueue = "Error"; @@ -284,7 +283,7 @@ public void DuplicateQueueNamesAreAllowedOnDifferentTransports_ShouldNotThrow() [Test] public void EnsureDuplicateQueueNamesAreAllowedOnSameTransportWithDifferentConnectionString() { - var newInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(GetZipFolder().FullName); + var newInstance = ServiceControlNewInstance.CreateWithDefaultPersistence(); newInstance.TransportPackage = ServiceControlCoreTransports.All.First(t => t.Name == TransportNames.RabbitMQQuorumConventionalRoutingTopology); newInstance.ErrorQueue = "RMQerror"; @@ -307,26 +306,6 @@ public void EnsureDuplicateQueueNamesAreAllowedOnSameTransportWithDifferentConne Assert.DoesNotThrow(() => p.CheckQueueNamesAreNotTakenByAnotherServiceControlInstance()); } - static DirectoryInfo GetZipFolder() - { - var currentFolder = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); - - while (currentFolder != null) - { - var file = currentFolder.EnumerateFiles("*.sln", SearchOption.TopDirectoryOnly) - .SingleOrDefault(); - - if (file != null) - { - return new DirectoryInfo(Path.Combine(file.Directory.Parent.FullName, "zip")); - } - - currentFolder = currentFolder.Parent; - } - - throw new Exception("Cannot find zip folder"); - } - List instances; } } diff --git a/src/ServiceControlInstaller.Engine.UnitTests/Validation/RuntimeNetVersionTest.cs b/src/ServiceControlInstaller.Engine.UnitTests/Validation/RuntimeNetVersionTest.cs new file mode 100644 index 0000000000..373d6af81d --- /dev/null +++ b/src/ServiceControlInstaller.Engine.UnitTests/Validation/RuntimeNetVersionTest.cs @@ -0,0 +1,104 @@ +namespace ServiceControlInstaller.Engine.UnitTests.Validation +{ + using System; + using System.IO; + using System.Linq; + using System.Xml; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using NUnit.Framework; + using ServiceControlInstaller.Engine.Validation; + + [TestFixture] + class RuntimeNetVersionTest + { + [Test] + public void EnsureCorrectRuntimeVersionIsShipped() + { + var raven = GetFromRavenServerRuntimeConfig(); + Console.WriteLine(raven); + + const string somethingWrong = "There is something wrong with the logic used to find the .NET version that RavenDB needs to run."; + Assert.IsNotNull(raven.NetRuntime, somethingWrong); + Assert.IsNotNull(raven.AspNetCore, somethingWrong); + + var minVersion = DotnetVersionValidator.MinimumVersionString; + + Console.WriteLine($"Minimum version requested by ServiceControl: {minVersion}"); + + var versionsOk = DotnetVersionValidator.DotnetVersionOk(raven.NetRuntime, minVersion) && DotnetVersionValidator.DotnetVersionOk(raven.AspNetCore, minVersion); + string howToFix = $"The .NET/AspNetCore runtime {minVersion} validated by the installer is incorrect and won't allow RavenDB to run. Update the DotnetVersionValidator.MinimumVersionString constant to a version matching or newer than what is needed by RavenDB. ({raven})"; + + Assert.IsTrue(versionsOk, howToFix); + Console.WriteLine("Versions are OK"); + } + + [Test] + public void TestValidatorLogic() + { + // Should always pass on CI because we download the latest available dotnet SDK + Assert.IsFalse(DotnetVersionValidator.FrameworkRequirementsAreMissing(true, out var _)); + } + + static RavenServerVersions GetFromRavenServerRuntimeConfig() + { + // First get RavenDB package version + var packageConfigPath = Path.GetFullPath(Path.Combine(TestContext.CurrentContext.TestDirectory, "..", "..", "..", "..", "Directory.Packages.props")); + + var doc = new XmlDocument(); + doc.Load(packageConfigPath); + + var ravenNode = doc.DocumentElement.SelectSingleNode("/Project/ItemGroup/PackageVersion[@Include='RavenDB.Embedded']"); + var ravenPackageVersion = ravenNode.Attributes["Version"].Value; + + // Now find Raven.Server.runtimeconfig.json + var nugetPath = Environment.GetEnvironmentVariable("NUGET_PACKAGES"); + + if (string.IsNullOrWhiteSpace(nugetPath)) + { + nugetPath = Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), ".nuget", "packages"); + } + + var packagePath = Path.Combine(nugetPath, "ravendb.embedded", ravenPackageVersion); + var contentFilesPath = Path.Combine(packagePath, "contentFiles"); + + var filePaths = Directory.GetFiles(contentFilesPath, "Raven.Server.runtimeconfig.json", SearchOption.AllDirectories); + + var firstFileText = File.ReadAllText(filePaths.FirstOrDefault()); + + var json = JsonConvert.DeserializeObject(firstFileText); + + var frameworks = json["runtimeOptions"]["frameworks"] as JArray; + + var versions = new RavenServerVersions(); + + foreach (var fw in frameworks.OfType()) + { + var name = fw["name"].Value(); + var versionString = fw["version"].Value(); + + if (Version.TryParse(versionString, out var version)) + { + if (name == "Microsoft.NETCore.App") + { + versions.NetRuntime = version; + } + else if (name == "Microsoft.AspNetCore.App") + { + versions.AspNetCore = version; + } + } + } + + return versions; + } + + class RavenServerVersions + { + public Version NetRuntime; + public Version AspNetCore; + + public override string ToString() => $"RavenServer Minimum Versions: NetCoreApp = {NetRuntime}, AspNetCore = {AspNetCore}"; + } + } +} diff --git a/src/ServiceControlInstaller.Engine.UnitTests/Zip/UnpackTest.cs b/src/ServiceControlInstaller.Engine.UnitTests/Zip/UnpackTest.cs index 6b62eac3c3..816d7266e8 100644 --- a/src/ServiceControlInstaller.Engine.UnitTests/Zip/UnpackTest.cs +++ b/src/ServiceControlInstaller.Engine.UnitTests/Zip/UnpackTest.cs @@ -52,8 +52,14 @@ public void FilterUnzipTest() { workingFolder.Delete(true); Directory.CreateDirectory(workingFolder.FullName); - FileUtils.UnzipToSubdirectory(zipFilePath, workingFolder.FullName, "B"); - FileUtils.UnzipToSubdirectory(zipFilePath, workingFolder.FullName, @"A\AA"); + using (var zipStream = File.OpenRead(zipFilePath)) + { + FileUtils.UnzipToSubdirectory(zipStream, workingFolder.FullName, "B"); + } + using (var zipStream = File.OpenRead(zipFilePath)) + { + FileUtils.UnzipToSubdirectory(zipStream, workingFolder.FullName, @"A\AA"); + } Assert.True(workingFolder.GetFiles().Count() == 2, "Should only be two file asub and broot"); Assert.True(workingFolder.GetFiles("asub*.txt").Count() == 1, "subfolder did not unpack to root"); Assert.True(workingFolder.GetDirectories().Count() == 1, "Should only be one directory after unpack"); diff --git a/src/ServiceControlInstaller.Engine.UnitTests/Zip/VersionTest.cs b/src/ServiceControlInstaller.Engine.UnitTests/Zip/VersionTest.cs deleted file mode 100644 index d72d7818c7..0000000000 --- a/src/ServiceControlInstaller.Engine.UnitTests/Zip/VersionTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace ServiceControlInstaller.Engine.UnitTests.Zip -{ - using System; - using System.IO; - using System.Linq; - using FileSystem; - using NUnit.Framework; - - [TestFixture] - public class VersionTest - { - [SetUp] - public void Setup() - { - Cleanup(); - foreach (var tag in new[] - { - "1.1.0", - "1.9.0", - "1.10.0", - "1.10.1-unstable", - "1.2-0" - }) - { - using (var x = File.CreateText(Path.Combine(Path.GetTempPath(), string.Format(basefilename, tag)))) - { - x.WriteLine("temp"); - } - } - } - - [Test] - public void FindVersionTest() - { - var zipInfo = ServiceControlZipInfo.Find(Path.GetTempPath()); - Assert.True(zipInfo.Present, "No zip file found but should be"); - Assert.True(zipInfo.Version == new Version("1.10.0"), "Incorrect version of zip found"); - } - - [TearDown] - public void TearDown() - { - Cleanup(); - } - - void Cleanup() - { - var dir = new DirectoryInfo(Path.GetTempPath()); - foreach (var file in dir.GetFiles(string.Format(basefilename, "*")).ToList()) - { - file.Delete(); - } - } - - const string basefilename = "particular.servicecontrol-{0}.zip"; - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine/FileSystem/FileUtils.cs b/src/ServiceControlInstaller.Engine/FileSystem/FileUtils.cs index 1a1afafe46..ae74eeda09 100644 --- a/src/ServiceControlInstaller.Engine/FileSystem/FileUtils.cs +++ b/src/ServiceControlInstaller.Engine/FileSystem/FileUtils.cs @@ -3,6 +3,7 @@ namespace ServiceControlInstaller.Engine.FileSystem using System; using System.IO; using System.Linq; + using System.Reflection; using System.Security.AccessControl; using Ionic.Zip; @@ -76,39 +77,46 @@ public static void CreateDirectoryAndSetAcl(string path, FileSystemAccessRule ac destination.SetAccessControl(accessRules); } - public static void UnzipToSubdirectory(string zipFilePath, string targetPath, string zipFolderNameToExtract) + public static void UnzipToSubdirectory(string zipResourceName, string targetPath, string zipFolderNameToExtract) { - using (var zip = ZipFile.Read(zipFilePath)) + var assembly = Assembly.GetExecutingAssembly(); + using var zipStream = assembly.GetManifestResourceStream(zipResourceName); + + UnzipToSubdirectory(zipStream, targetPath, zipFolderNameToExtract); + } + + internal static void UnzipToSubdirectory(Stream zipStream, string targetPath, string zipFolderNameToExtract) + { + using var zip = ZipFile.Read(zipStream); + + foreach (var e in zip) { - foreach (var e in zip) + var dir = Path.GetDirectoryName(e.FileName); + string filename = null; + + if (dir.StartsWith(zipFolderNameToExtract, StringComparison.OrdinalIgnoreCase)) { - var dir = Path.GetDirectoryName(e.FileName); - string filename = null; + filename = Path.Combine(targetPath, e.FileName.Substring(zipFolderNameToExtract.Length + 1)); + } + + if (filename != null) + { + if (e.IsDirectory) + { + Directory.CreateDirectory(filename); + continue; + } - if (dir.StartsWith(zipFolderNameToExtract, StringComparison.OrdinalIgnoreCase)) + // Ensure folder exists + var folder = Path.GetDirectoryName(filename); + if (!Directory.Exists(folder)) { - filename = Path.Combine(targetPath, e.FileName.Substring(zipFolderNameToExtract.Length + 1)); + Directory.CreateDirectory(folder); } - if (filename != null) + using (var stream = new FileStream(filename, FileMode.OpenOrCreate)) { - if (e.IsDirectory) - { - Directory.CreateDirectory(filename); - continue; - } - - // Ensure folder exists - var folder = Path.GetDirectoryName(filename); - if (!Directory.Exists(folder)) - { - Directory.CreateDirectory(folder); - } - - using (var stream = new FileStream(filename, FileMode.OpenOrCreate)) - { - e.Extract(stream); - } + e.Extract(stream); } } } diff --git a/src/ServiceControlInstaller.Engine/FileSystem/MonitoringZipInfo.cs b/src/ServiceControlInstaller.Engine/FileSystem/MonitoringZipInfo.cs deleted file mode 100644 index f6c2e7250a..0000000000 --- a/src/ServiceControlInstaller.Engine/FileSystem/MonitoringZipInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace ServiceControlInstaller.Engine.FileSystem -{ - using Instances; - - public static class MonitoringZipInfo - { - /// - /// Find the latest servicecontrol zip based on the version number in the file name - file name must be in form - /// particular.servicecontrol-<major>.<minor>.<patch>.zip - /// - public static PlatformZipInfo Find(string deploymentCachePath) => - new PlatformZipInfoFinder( - "particular.servicecontrol.monitoring", - Constants.MonitoringExe, - "ServiceControl Monitoring" - ).Find(deploymentCachePath); - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine/FileSystem/PlatformZipInfo.cs b/src/ServiceControlInstaller.Engine/FileSystem/PlatformZipInfo.cs index 09fef440a8..f0b7876e60 100644 --- a/src/ServiceControlInstaller.Engine/FileSystem/PlatformZipInfo.cs +++ b/src/ServiceControlInstaller.Engine/FileSystem/PlatformZipInfo.cs @@ -1,24 +1,39 @@ namespace ServiceControlInstaller.Engine.FileSystem { using System; + using System.Reflection; public class PlatformZipInfo { - public static PlatformZipInfo Empty = new PlatformZipInfo(string.Empty, string.Empty, string.Empty, null, false); - - public PlatformZipInfo(string mainEntrypoint, string name, string filePath, Version version, bool present) + public PlatformZipInfo(string mainEntrypoint, string name, string zipResourceName, Version version) { Name = name; MainEntrypoint = mainEntrypoint; - FilePath = filePath; + ResourceName = zipResourceName; Version = version; - Present = present; } public string MainEntrypoint { get; } + public string Name { get; } - public string FilePath { get; } + + public string ResourceName { get; } + public Version Version { get; } - public bool Present { get; } + + public void ValidateZip() + { + if (string.IsNullOrEmpty(ResourceName)) + { + throw new Exception("Empty zip file resource name"); + } + + var resourceInfo = Assembly.GetExecutingAssembly().GetManifestResourceInfo(ResourceName); + + if (resourceInfo is null) + { + throw new Exception($"Missing zip file resource '{ResourceName}'"); + } + } } } \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine/FileSystem/PlatformZipInfoExtensions.cs b/src/ServiceControlInstaller.Engine/FileSystem/PlatformZipInfoExtensions.cs deleted file mode 100644 index 93fad7471e..0000000000 --- a/src/ServiceControlInstaller.Engine/FileSystem/PlatformZipInfoExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace ServiceControlInstaller.Engine.FileSystem -{ - using System; - using System.IO; - using Ionic.Zip; - - public static class PlatformZipInfoExtensions - { - public static void ValidateZip(this PlatformZipInfo zipInfo) - { - if (!zipInfo.Present) - { - throw new FileNotFoundException($"No Zip file found at: {zipInfo.FilePath}"); - } - - if (!ZipFile.CheckZip(zipInfo.FilePath)) - { - throw new Exception($"Corrupt Zip File - {zipInfo.FilePath}"); - } - } - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine/FileSystem/PlatformZipInfoFinder.cs b/src/ServiceControlInstaller.Engine/FileSystem/PlatformZipInfoFinder.cs deleted file mode 100644 index 4237535e6e..0000000000 --- a/src/ServiceControlInstaller.Engine/FileSystem/PlatformZipInfoFinder.cs +++ /dev/null @@ -1,75 +0,0 @@ -namespace ServiceControlInstaller.Engine.FileSystem -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text.RegularExpressions; - - class PlatformZipInfoFinder - { - readonly string mainEntryPoint; - readonly string name; - readonly Regex fileRegex; - readonly string searchPattern; - - public PlatformZipInfoFinder(string packageFileNamePrefix, string mainEntryPoint, string name) - { - this.mainEntryPoint = mainEntryPoint; - this.name = name; - fileRegex = new Regex(packageFileNamePrefix + @"-(?\d+\.\d+\.\d+)\.zip", RegexOptions.Compiled | RegexOptions.IgnoreCase); - searchPattern = packageFileNamePrefix + "-*.zip"; - } - - public PlatformZipInfo Find(string deploymentCachePath) => Find(new DirectoryInfo(deploymentCachePath)); - - PlatformZipInfo Find(DirectoryInfo deploymentCache) - { - for (var folder = deploymentCache; folder != null; folder = folder.Parent) - { - var fromHere = Get(folder); - if (fromHere != null) - { - return fromHere; - } - - var zipFolder = folder.EnumerateDirectories("zip", SearchOption.TopDirectoryOnly).FirstOrDefault(); - if (zipFolder != null) - { - var fromZipFolder = Get(zipFolder); - if (fromZipFolder != null) - { - return fromZipFolder; - } - } - } - - return PlatformZipInfo.Empty; - } - - PlatformZipInfo Get(DirectoryInfo deploymentCache) - { - var list = new Dictionary(); - - foreach (var file in deploymentCache.GetFiles(searchPattern, SearchOption.TopDirectoryOnly)) - { - var matchResult = fileRegex.Match(file.Name); - if (!matchResult.Success) - { - continue; - } - - var v = new Version(matchResult.Groups["version"].Value); - list.Add(file.FullName, v); - } - - if (list.Count == 0) - { - return null; - } - - var latest = list.OrderByDescending(p => p.Value).First(); - return new PlatformZipInfo(mainEntryPoint, name, filePath: latest.Key, version: latest.Value, present: true); - } - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine/FileSystem/ServiceControlAuditZipInfo.cs b/src/ServiceControlInstaller.Engine/FileSystem/ServiceControlAuditZipInfo.cs deleted file mode 100644 index e1a931e822..0000000000 --- a/src/ServiceControlInstaller.Engine/FileSystem/ServiceControlAuditZipInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace ServiceControlInstaller.Engine.FileSystem -{ - using Instances; - - public static class ServiceControlAuditZipInfo - { - public static PlatformZipInfo Find(string deploymentCachePath) => - new PlatformZipInfoFinder( - "particular.servicecontrol.audit", - Constants.ServiceControlAuditExe, - "ServiceControl Audit" - ).Find(deploymentCachePath); - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine/FileSystem/ServiceControlZipInfo.cs b/src/ServiceControlInstaller.Engine/FileSystem/ServiceControlZipInfo.cs deleted file mode 100644 index 43e2e1ee9d..0000000000 --- a/src/ServiceControlInstaller.Engine/FileSystem/ServiceControlZipInfo.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace ServiceControlInstaller.Engine.FileSystem -{ - using Instances; - - public static class ServiceControlZipInfo - { - public static PlatformZipInfo Find(string deploymentCachePath) => - new PlatformZipInfoFinder("particular.servicecontrol", - Constants.ServiceControlExe, - "ServiceControl" - ).Find(deploymentCachePath); - } -} \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine/Instances/Constants.cs b/src/ServiceControlInstaller.Engine/Instances/Constants.cs index ad146f1385..9b0cddb3a2 100644 --- a/src/ServiceControlInstaller.Engine/Instances/Constants.cs +++ b/src/ServiceControlInstaller.Engine/Instances/Constants.cs @@ -1,9 +1,30 @@ namespace ServiceControlInstaller.Engine.Instances { - class Constants + using System; + using System.Reflection; + + public static class Constants { public const string ServiceControlExe = "ServiceControl.exe"; public const string ServiceControlAuditExe = "ServiceControl.Audit.exe"; public const string MonitoringExe = "ServiceControl.Monitoring.exe"; + + public static Version CurrentVersion { get; } + + static Constants() + { + var attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(); + Version majorMinorPatch = null; + + foreach (var attribute in attributes) + { + if (attribute.Key == "MajorMinorPatch") + { + majorMinorPatch = new Version(attribute.Value); + } + } + + CurrentVersion = majorMinorPatch; + } } } \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine/Instances/MonitoringNewInstance.cs b/src/ServiceControlInstaller.Engine/Instances/MonitoringNewInstance.cs index 6e59a9a4e3..afae2e2c20 100644 --- a/src/ServiceControlInstaller.Engine/Instances/MonitoringNewInstance.cs +++ b/src/ServiceControlInstaller.Engine/Instances/MonitoringNewInstance.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; - using System.Reflection; using System.Security.AccessControl; using System.Security.Principal; using System.Threading.Tasks; @@ -21,9 +20,7 @@ public class MonitoringNewInstance : IMonitoringInstance { public MonitoringNewInstance() { - var appDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - var zipInfo = MonitoringZipInfo.Find(appDirectory); - Version = zipInfo.Version; + Version = Constants.CurrentVersion; } public ReportCard ReportCard { get; set; } @@ -67,7 +64,7 @@ string[] FlagFiles public string BrowsableUrl => throw new NotImplementedException("Not available until the instance is installed"); - public void CopyFiles(string zipFilePath) + public void CopyFiles(string zipResourceName) { //Clear out any files from previous runs of Add Instance, just in case user switches transport //Validation checks for the flag file so wont get here if the directory was also changed @@ -83,15 +80,15 @@ public void CopyFiles(string zipFilePath) FileUtils.CreateDirectoryAndSetAcl(LogPath, modifyAccessRule); } - // Mark these directories with a flag + // Mark these directories with a flag // These flags indicate the directory is empty check can be ignored // We need this because if an install screws up and doesn't complete it is ok to overwrite on a subsequent attempt // First run will still the check AddFlagFiles(); // Copy the binaries from a zip - FileUtils.UnzipToSubdirectory(zipFilePath, InstallPath, "ServiceControl.Monitoring"); - FileUtils.UnzipToSubdirectory(zipFilePath, InstallPath, $@"Transports\{TransportPackage.ZipName}"); + FileUtils.UnzipToSubdirectory(zipResourceName, InstallPath, "ServiceControl.Monitoring"); + FileUtils.UnzipToSubdirectory(zipResourceName, InstallPath, $@"Transports\{TransportPackage.ZipName}"); } public void WriteConfigurationFile() diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlAuditInstance.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlAuditInstance.cs index d1027e2481..c4c8c75e3a 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlAuditInstance.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlAuditInstance.cs @@ -5,7 +5,6 @@ namespace ServiceControlInstaller.Engine.Instances using System.Configuration; using System.IO; using System.Linq; - using System.Reflection; using Configuration; using Configuration.ServiceControl; using FileSystem; @@ -14,14 +13,8 @@ namespace ServiceControlInstaller.Engine.Instances public class ServiceControlAuditInstance : ServiceControlBaseService, IServiceControlAuditInstance { - public ServiceControlAuditInstance(IWindowsServiceController service) : this(service, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)) + public ServiceControlAuditInstance(IWindowsServiceController service) : base(service) { - } - - public ServiceControlAuditInstance(IWindowsServiceController service, string deploymentCachePath) : base(service) - { - this.deploymentCachePath = deploymentCachePath; - Reload(); } @@ -80,9 +73,7 @@ public override void Reload() InMaintenanceMode = AppConfig.Read(AuditInstanceSettingsList.MaintenanceMode, false); ServiceControlQueueAddress = AppConfig.Read(AuditInstanceSettingsList.ServiceControlQueueAddress, null); - var zipInfo = ServiceControlAuditZipInfo.Find(deploymentCachePath); - - var manifests = ServiceControlPersisters.LoadAllManifests(zipInfo.FilePath); + var manifests = ServiceControlPersisters.AuditPersistenceManifests; var persistenceType = AppConfig.Read(AuditInstanceSettingsList.PersistenceType, null); @@ -114,12 +105,12 @@ public override void RunQueueCreation() { QueueCreation.RunQueueCreation(this); } - public override void UpgradeFiles(string zipFilePath) + public override void UpgradeFiles(string zipResourceName) { FileUtils.DeleteDirectory(InstallPath, true, true, "license", $"{Constants.ServiceControlAuditExe}.config"); - FileUtils.UnzipToSubdirectory(zipFilePath, InstallPath, BaseServiceName); - FileUtils.UnzipToSubdirectory(zipFilePath, InstallPath, $@"Transports\{TransportPackage.ZipName}"); - FileUtils.UnzipToSubdirectory(zipFilePath, InstallPath, $@"Persisters\{PersistenceManifest.Name}"); + FileUtils.UnzipToSubdirectory(zipResourceName, InstallPath, BaseServiceName); + FileUtils.UnzipToSubdirectory(zipResourceName, InstallPath, $@"Transports\{TransportPackage.ZipName}"); + FileUtils.UnzipToSubdirectory(zipResourceName, InstallPath, $@"Persisters\{PersistenceManifest.Name}"); } protected override IEnumerable GetPersistencePathsToCleanUp() @@ -138,7 +129,5 @@ protected override IEnumerable GetPersistencePathsToCleanUp() : folderpath; } } - - readonly string deploymentCachePath; } } \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlAuditNewInstance.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlAuditNewInstance.cs index 0e982cda19..3155275625 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlAuditNewInstance.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlAuditNewInstance.cs @@ -3,7 +3,6 @@ using System; using System.IO; using System.Linq; - using System.Reflection; using System.Xml; using System.Xml.Serialization; using Configuration.ServiceControl; @@ -15,29 +14,23 @@ public class ServiceControlAuditNewInstance : ServiceControlInstallableBase, IServiceControlAuditInstance { public static ServiceControlAuditNewInstance CreateWithDefaultPersistence() - { - return CreateWithDefaultPersistence(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); - } - - public static ServiceControlAuditNewInstance CreateWithDefaultPersistence(string deploymentCachePath) { const string persisterToUseForBrandNewInstances = StorageEngineNames.RavenDB5; - return CreateWithPersistence(deploymentCachePath, persisterToUseForBrandNewInstances); + return CreateWithPersistence(persisterToUseForBrandNewInstances); } - public static ServiceControlAuditNewInstance CreateWithPersistence(string deploymentCachePath, string persistence) + public static ServiceControlAuditNewInstance CreateWithPersistence(string persistence) { - var zipInfo = ServiceControlAuditZipInfo.Find(deploymentCachePath); - var persistenceManifest = ServiceControlPersisters.LoadAllManifests(zipInfo.FilePath) + var persistenceManifest = ServiceControlPersisters.AuditPersistenceManifests .Single(manifest => manifest.Name == persistence); - return new ServiceControlAuditNewInstance(zipInfo.Version, persistenceManifest); + return new ServiceControlAuditNewInstance(persistenceManifest); } - public ServiceControlAuditNewInstance(Version version, PersistenceManifest persistenceManifest) + public ServiceControlAuditNewInstance(PersistenceManifest persistenceManifest) { - Version = version; + Version = Constants.CurrentVersion; PersistenceManifest = persistenceManifest; } @@ -93,11 +86,11 @@ protected override void ValidateConnectionString() ConnectionStringValidator.Validate(this); } - public override void CopyFiles(string zipFilePath) + public override void CopyFiles(string zipResourceName) { - base.CopyFiles(zipFilePath); + base.CopyFiles(zipResourceName); - FileUtils.UnzipToSubdirectory(zipFilePath, InstallPath, $@"Persisters\{PersistenceManifest.Name}"); + FileUtils.UnzipToSubdirectory(zipResourceName, InstallPath, $@"Persisters\{PersistenceManifest.Name}"); } } } \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs index 972e20a446..d3c9f95e44 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs @@ -396,7 +396,7 @@ public void RestoreAppConfig(string sourcePath) AppConfig.Save(); } - public abstract void UpgradeFiles(string zipFilePath); + public abstract void UpgradeFiles(string zipResourceName); public abstract void RunQueueCreation(); diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs index 648da9945d..2d6dc1615f 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs @@ -111,9 +111,9 @@ public string Url public string BrowsableUrl => throw new NotImplementedException("Not available until the instance is installed"); - public virtual void CopyFiles(string zipFilePath) + public virtual void CopyFiles(string zipResourceName) { - //Clear out any files from previos runs of Add Instance, just in case user switches transport + //Clear out any files from previous runs of Add Instance, just in case user switches transport //Validation checks for the flag file so wont get here if the directory was also changed FileUtils.DeleteDirectory(InstallPath, true, true); @@ -139,8 +139,8 @@ public virtual void CopyFiles(string zipFilePath) AddFlagFiles(); // Copy the binaries from a zip - FileUtils.UnzipToSubdirectory(zipFilePath, InstallPath, DirectoryName); - FileUtils.UnzipToSubdirectory(zipFilePath, InstallPath, $@"Transports\{TransportPackage.ZipName}"); + FileUtils.UnzipToSubdirectory(zipResourceName, InstallPath, DirectoryName); + FileUtils.UnzipToSubdirectory(zipResourceName, InstallPath, $@"Transports\{TransportPackage.ZipName}"); } public virtual void WriteConfigurationFile() diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs index 07b7ddf035..7bcdacbfc5 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs @@ -5,7 +5,6 @@ namespace ServiceControlInstaller.Engine.Instances using System.Configuration; using System.IO; using System.Linq; - using System.Reflection; using System.Threading.Tasks; using Configuration; using Configuration.ServiceControl; @@ -16,15 +15,8 @@ namespace ServiceControlInstaller.Engine.Instances public class ServiceControlInstance : ServiceControlBaseService, IServiceControlInstance { - public ServiceControlInstance(IWindowsServiceController service) - : this(service, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)) + public ServiceControlInstance(IWindowsServiceController service) : base(service) { - } - - public ServiceControlInstance(IWindowsServiceController service, string deploymentCachePath) : base(service) - { - this.deploymentCachePath = deploymentCachePath; - Reload(); } @@ -121,8 +113,7 @@ public override void Reload() Description = GetDescription(); ServiceAccount = Service.Account; - var zipInfo = ServiceControlZipInfo.Find(deploymentCachePath); - var manifests = ServiceControlPersisters.LoadAllManifests(zipInfo.FilePath); + var manifests = ServiceControlPersisters.PrimaryPersistenceManifests; var persistenceType = AppConfig.Read(ServiceControlSettings.PersistenceType, null); if (string.IsNullOrEmpty(persistenceType)) @@ -240,7 +231,5 @@ protected override IEnumerable GetPersistencePathsToCleanUp() : folderpath; } } - - readonly string deploymentCachePath; } } \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlNewInstance.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlNewInstance.cs index d41e7b0ad8..4a5d1731b0 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlNewInstance.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlNewInstance.cs @@ -4,7 +4,6 @@ namespace ServiceControlInstaller.Engine.Instances using System.Collections.Generic; using System.IO; using System.Linq; - using System.Reflection; using System.Xml; using System.Xml.Serialization; using Configuration.ServiceControl; @@ -16,28 +15,22 @@ namespace ServiceControlInstaller.Engine.Instances public class ServiceControlNewInstance : ServiceControlInstallableBase, IServiceControlInstance { public static ServiceControlNewInstance CreateWithDefaultPersistence() - { - return CreateWithDefaultPersistence(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); - } - - public static ServiceControlNewInstance CreateWithDefaultPersistence(string deploymentCachePath) { const string persisterUsedForBrandNewInstances = StorageEngineNames.RavenDB5; - return CreateWithPersistence(deploymentCachePath, persisterUsedForBrandNewInstances); + return CreateWithPersistence(persisterUsedForBrandNewInstances); } - public static ServiceControlNewInstance CreateWithPersistence(string deploymentCachePath, string persistence) + public static ServiceControlNewInstance CreateWithPersistence(string persistence) { - var zipInfo = ServiceControlZipInfo.Find(deploymentCachePath); - var persistenceManifest = ServiceControlPersisters.LoadAllManifests(zipInfo.FilePath) + var persistenceManifest = ServiceControlPersisters.PrimaryPersistenceManifests .Single(manifest => manifest.Name == persistence); - return new ServiceControlNewInstance(zipInfo.Version, persistenceManifest); + return new ServiceControlNewInstance(persistenceManifest); } - public ServiceControlNewInstance(Version version, PersistenceManifest persistenceManifest) + public ServiceControlNewInstance(PersistenceManifest persistenceManifest) { - Version = version; + Version = Constants.CurrentVersion; PersistenceManifest = persistenceManifest; } diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlPersisters.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlPersisters.cs index 7dc9683ac2..ad95037ead 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlPersisters.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlPersisters.cs @@ -2,33 +2,46 @@ { using System.Collections.Generic; using System.IO; - using System.IO.Compression; using System.Linq; + using System.Reflection; using Newtonsoft.Json; - public class ServiceControlPersisters + public static class ServiceControlPersisters { - public static IReadOnlyList LoadAllManifests(string zipFilePath) + public static PersistenceManifest[] PrimaryPersistenceManifests { get; } + public static PersistenceManifest[] AuditPersistenceManifests { get; } + + static ServiceControlPersisters() { - using (var zipArchive = ZipFile.OpenRead(zipFilePath)) - { - var manifests = new List(); + var assembly = Assembly.GetExecutingAssembly(); + var list = new List(); + + var resourceNames = assembly.GetManifestResourceNames() + .Where(name => name.EndsWith("persistence.manifest")) + .ToArray(); - var persistenceManifests = zipArchive.Entries.Where(e => e.Name == "persistence.manifest"); + PrimaryPersistenceManifests = resourceNames + .Where(name => name.StartsWith(@"Particular.ServiceControl\Persisters")) + .Select(name => Load(assembly, name)) + .OrderBy(m => m.Name) + .ToArray(); - foreach (var manifestEntry in persistenceManifests) - { - using (var stream = manifestEntry.Open()) - using (var reader = new StreamReader(stream)) - { - var manifestContent = reader.ReadToEnd(); - var manifest = JsonConvert.DeserializeObject(manifestContent); + AuditPersistenceManifests = resourceNames + .Where(name => name.StartsWith(@"Particular.ServiceControl.Audit\Persisters")) + .Select(name => Load(assembly, name)) + .OrderBy(m => m.Name) + .ToArray(); + } - manifests.Add(manifest); - } - } + static PersistenceManifest Load(Assembly assembly, string resourceName) + { + using (var stream = assembly.GetManifestResourceStream(resourceName)) + using (var reader = new StreamReader(stream)) + { + var manifestContent = reader.ReadToEnd(); + var manifest = JsonConvert.DeserializeObject(manifestContent); - return manifests; + return manifest; } } } diff --git a/src/ServiceControlInstaller.Engine/ServiceControlInstaller.Engine.csproj b/src/ServiceControlInstaller.Engine/ServiceControlInstaller.Engine.csproj index a42224d85d..adf224c1c5 100644 --- a/src/ServiceControlInstaller.Engine/ServiceControlInstaller.Engine.csproj +++ b/src/ServiceControlInstaller.Engine/ServiceControlInstaller.Engine.csproj @@ -28,12 +28,14 @@
- - - ..\..\zip\ - - - + + + + + + + +
\ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine/Unattended/UnattendAuditInstaller.cs b/src/ServiceControlInstaller.Engine/Unattended/UnattendAuditInstaller.cs index 9bd5ff5da1..d1538dfd3a 100644 --- a/src/ServiceControlInstaller.Engine/Unattended/UnattendAuditInstaller.cs +++ b/src/ServiceControlInstaller.Engine/Unattended/UnattendAuditInstaller.cs @@ -1,7 +1,6 @@ namespace ServiceControlInstaller.Engine.Unattended { using System; - using System.IO; using System.ServiceProcess; using System.Threading.Tasks; using FileSystem; @@ -15,15 +14,7 @@ public class UnattendAuditInstaller public UnattendAuditInstaller(ILogging loggingInstance) { logger = new Logging(loggingInstance); - var sourceroot = Path.GetFullPath(Environment.ExpandEnvironmentVariables(".")); - ZipInfo = ServiceControlAuditZipInfo.Find(sourceroot); - } - - public UnattendAuditInstaller(ILogging loggingInstance, string deploymentCachePath) - { - logger = new Logging(loggingInstance); - var sourceroot = Path.GetFullPath(Environment.ExpandEnvironmentVariables(deploymentCachePath)); - ZipInfo = ServiceControlAuditZipInfo.Find(sourceroot); + ZipInfo = new PlatformZipInfo(Constants.ServiceControlAuditExe, "ServiceControl Audit", "Particular.ServiceControl.Audit.zip", Constants.CurrentVersion); } public PlatformZipInfo ZipInfo { get; } @@ -57,7 +48,7 @@ public async Task Add(ServiceControlAuditNewInstance details, Func Add(MonitoringNewInstance details, Func Add(ServiceControlNewInstance details, Func Split(ServiceControlInstance instance, Options options, Func> pathToProceed) diff --git a/src/ServiceControlInstaller.Engine/Validation/DotnetVersionValidator.cs b/src/ServiceControlInstaller.Engine/Validation/DotnetVersionValidator.cs new file mode 100644 index 0000000000..b1adbe69a7 --- /dev/null +++ b/src/ServiceControlInstaller.Engine/Validation/DotnetVersionValidator.cs @@ -0,0 +1,99 @@ +namespace ServiceControlInstaller.Engine.Validation +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.Win32; + + public static class DotnetVersionValidator + { + public const string MinimumVersionString = "7.0.9"; + + public static bool FrameworkRequirementsAreMissing(bool needsRavenDB, out string message) + { + message = null; + + var dotnetMinVersion = Version.Parse(MinimumVersionString); + var majorMinor = $"{dotnetMinVersion.Major}.{dotnetMinVersion.Minor}"; + + if (!Environment.Is64BitProcess) + { + message = "ServiceControl can only be installed on a 64-bit OS."; + return true; + } + + var missing = new List(); + using var registry = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64); + + using var netFxKey = registry.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full"); + var netFxValue = netFxKey?.GetValue("Release") as int?; + if (netFxValue is null or < 461813) + { + missing.Add(".NET Framework 4.7.2 or later: Download from https://dotnet.microsoft.com/download/dotnet-framework"); + } + + if (needsRavenDB) + { + using var visualCppRedistributableKey = registry.OpenSubKey(@"SOFTWARE\Microsoft\DevDiv\VC\Servicing\14.0\RuntimeMinimum"); + var visualCppRedistributableVersion = visualCppRedistributableKey?.GetValue("Version") as string; + if (!Version.TryParse(visualCppRedistributableVersion, out var version) || version < new Version(14, 26, 28720)) + { + missing.Add("Visual C++ Redistributable for Visual Studio 2015-2019 x64: Download from https://aka.ms/vs/17/release/vc_redist.x64.exe"); + } + + using var dotnetKey = registry.OpenSubKey(@"SOFTWARE\dotnet\Setup\InstalledVersions\x64\sharedfx\Microsoft.NETCore.App") + ?? registry.OpenSubKey(@"SOFTWARE\WOW6432NODE\dotnet\Setup\InstalledVersions\x64\sharedfx\Microsoft.NETCore.App"); + + var dotnetNames = dotnetKey?.GetValueNames() ?? Array.Empty(); + var dotnetClosest = HighestMatchingMajorMinor(dotnetMinVersion, dotnetNames); + var dotnetOk = DotnetVersionOk(dotnetMinVersion, dotnetClosest); + if (!dotnetOk) + { + var foundString = dotnetClosest != null ? $", found {dotnetClosest}" : string.Empty; + missing.Add($".NET {dotnetMinVersion.Major} Runtime (x64), requires {dotnetMinVersion} or greater, found {dotnetClosest}: Download from https://dotnet.microsoft.com/download/dotnet/{majorMinor}"); + } + + using var aspNetKey = registry.OpenSubKey($@"SOFTWARE\Microsoft\ASP.NET Core\Shared Framework"); + var aspNetVersions = aspNetKey.GetSubKeyNames() + .Where(key => key.StartsWith("v")) + .SelectMany(key => aspNetKey.OpenSubKey(key).GetSubKeyNames()) + .ToArray(); + + var aspClosest = HighestMatchingMajorMinor(dotnetMinVersion, aspNetVersions); + var aspOk = DotnetVersionOk(dotnetMinVersion, aspClosest); + if (!aspOk) + { + var foundString = aspClosest != null ? $", found {aspClosest}" : string.Empty; + missing.Add($"ASP.NET Core {dotnetMinVersion.Major} Runtime (x64), requires {dotnetMinVersion} or greater{foundString}: Download from https://dotnet.microsoft.com/download/dotnet/{majorMinor}"); + } + } + + if (missing.Count > 0) + { + message = "ServiceControl cannot be installed because the system is missing the following prerequisites:" + + string.Join(string.Empty, missing.Select(msg => $"{Environment.NewLine} - {msg}")); + + return true; + } + + return false; + } + + static string HighestMatchingMajorMinor(Version requestedVersion, string[] versionChoices) + { + return versionChoices.Select(v => Version.TryParse(v, out var version) ? version : null) + .Where(v => v != null && v.Major <= requestedVersion.Major) + .OrderByDescending(v => v) + .FirstOrDefault() + ?.ToString(); + } + + internal static bool DotnetVersionOk(Version requestedVersion, string actualVersionString) + { + return Version.TryParse(actualVersionString, out var actualVersion) + && actualVersion.Major == requestedVersion.Major + && actualVersion.Minor == requestedVersion.Minor + && actualVersion >= requestedVersion; + } + } +} diff --git a/src/ServiceControlInstaller.Packaging.UnitTests/RuntimeNetVersionTest.cs b/src/ServiceControlInstaller.Packaging.UnitTests/RuntimeNetVersionTest.cs deleted file mode 100644 index bfac7b132e..0000000000 --- a/src/ServiceControlInstaller.Packaging.UnitTests/RuntimeNetVersionTest.cs +++ /dev/null @@ -1,155 +0,0 @@ -namespace ServiceControlInstaller.Packaging.UnitTests -{ - using System; - using System.IO; - using System.Linq; - using System.Xml; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - using NUnit.Framework; - - [TestFixture] - class RuntimeNetVersionTest - { - [Test] - public void EnsureCorrectRuntimeVersionIsShipped() - { - var aip = GetFromAipFile(); - var raven = GetFromRavenServerRuntimeConfig(); - - Console.WriteLine(aip); - Console.WriteLine(raven); - - const string somethingWrong = "There is something wrong with the logic used to find the .NET version that RavenDB needs the installer to install."; - - Assert.IsNotNull(aip.NetRuntimeMin, somethingWrong); - Assert.IsNotNull(aip.NetRuntimeMax, somethingWrong); - Assert.IsNotNull(aip.AspNetCoreMin, somethingWrong); - Assert.IsNotNull(aip.NetRuntimeToInstall, somethingWrong); - Assert.IsNotNull(aip.AspNetCoreToInstall, somethingWrong); - - Assert.IsNotNull(raven.NetRuntime, somethingWrong); - Assert.IsNotNull(raven.AspNetCore, somethingWrong); - - string howToFix = $"The .NET/AspNetCore runtime that the installer wants to install is too old. Update the AIP file in the AdvancedInstaller software as required by the current version of RavenDB. ({raven})"; - - // Major versions must be the same - Assert.That(aip.NetRuntimeMin.Major == raven.NetRuntime.Major && aip.NetRuntimeMax.Major == raven.NetRuntime.Major, howToFix); - Assert.That(aip.AspNetCoreMin.Major == raven.AspNetCore.Major, howToFix); - - // And within that major, versions installed must be the same or higher than what Raven requires - Assert.That(aip.NetRuntimeMin >= raven.NetRuntime, howToFix); - Assert.That(aip.NetRuntimeMax >= raven.NetRuntime, howToFix); - Assert.That(aip.AspNetCoreMin >= raven.AspNetCore, howToFix); - - // And check the AIP file as well - Assert.That(aip.NetRuntimeMin.Major == aip.NetRuntimeMax.Major, "Min and max versions should be on the same major version"); - Assert.That(aip.NetRuntimeMax > aip.NetRuntimeMin, "The maximum .NET version needs to be higher than the minimum .NET version. The upper bound is INCLUSIVE so you can't use the next major like with a NuGet package. Use something like `Major.Minor.100` i.e. `7.0.100`."); - Assert.That(aip.NetRuntimeToInstall == aip.NetRuntimeMin, "The .NET version to install needs to be the same as the check for which version to install."); - Assert.That(aip.AspNetCoreToInstall == aip.AspNetCoreMin, "The AspNet version to install needs to be the same as the check for which version to install."); - - // Check convention that the maximum version is patch 100 - Assert.That(aip.NetRuntimeMax.Build == 100, "For the maximum .NET runtime version, use 100 as the patch value. (i.e. 7.0.100)"); - - // Check that the ASPNET registry key in the AIP file is also correct - var desiredRegistryKeyEnding = "v" + aip.AspNetCoreMin.ToString(2); - Assert.That(aip.AspNetCoreRegistryKey, Does.EndWith(desiredRegistryKeyEnding), "The SearchString registry key must match the AspNetCore major+minor version."); - } - - - static AipVersions GetFromAipFile() - { - // P:\ServiceControl\src\ServiceControlInstaller.Packaging.UnitTests\bin\Debug\net472 - var aipPath = Path.GetFullPath(Path.Combine(TestContext.CurrentContext.TestDirectory, "..", "..", "..", "..", "Setup", "ServiceControl.aip")); - - var doc = new XmlDocument(); - doc.Load(aipPath); - - var versions = new AipVersions(); - - var runtimeNode = doc.DocumentElement.SelectSingleNode("/DOCUMENT/COMPONENT/ROW[@SearchKey='C6F7BF650B714DC58735D62C12F214E4M_1']"); - Version.TryParse(runtimeNode.Attributes["VerMin"].Value, out versions.NetRuntimeMin); - Version.TryParse(runtimeNode.Attributes["VerMax"].Value, out versions.NetRuntimeMax); - - var aspNode = doc.DocumentElement.SelectSingleNode("/DOCUMENT/COMPONENT/ROW[@SearchKey='E25D6A62194038942640BDE651049CASP.N']"); - Version.TryParse(aspNode.Attributes["VerMin"].Value, out versions.AspNetCoreMin); - versions.AspNetCoreRegistryKey = aspNode.Attributes["SearchString"].Value; - - var runtimeToInstallNode = doc.DocumentElement.SelectSingleNode("/DOCUMENT/COMPONENT/ROW[@PrereqKey='C6F7BF650B714DC58735D62C12F214E4']"); - Version.TryParse(runtimeToInstallNode.Attributes["VersionMin"].Value, out versions.NetRuntimeToInstall); - - var aspnetToInstallNode = doc.DocumentElement.SelectSingleNode("/DOCUMENT/COMPONENT/ROW[@PrereqKey='E25D6A62194038942640BDE651049C']"); - Version.TryParse(aspnetToInstallNode.Attributes["VersionMin"].Value, out versions.AspNetCoreToInstall); - - return versions; - } - - static RavenServerVersions GetFromRavenServerRuntimeConfig() - { - // First get RavenDB package version - var packageConfigPath = Path.GetFullPath(Path.Combine(TestContext.CurrentContext.TestDirectory, "..", "..", "..", "..", "Directory.Packages.props")); - - var doc = new XmlDocument(); - doc.Load(packageConfigPath); - - var ravenNode = doc.DocumentElement.SelectSingleNode("/Project/ItemGroup/PackageVersion[@Include='RavenDB.Embedded']"); - var ravenPackageVersion = ravenNode.Attributes["Version"].Value; - - // Now find Raven.Server.runtimeconfig.json - var nugetPath = Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), ".nuget", "packages"); - var packagePath = Path.Combine(nugetPath, "ravendb.embedded", ravenPackageVersion); - var contentFilesPath = Path.Combine(packagePath, "contentFiles"); - - var filePaths = Directory.GetFiles(contentFilesPath, "Raven.Server.runtimeconfig.json", SearchOption.AllDirectories); - - var firstFileText = File.ReadAllText(filePaths.FirstOrDefault()); - - var json = JsonConvert.DeserializeObject(firstFileText); - - var frameworks = json["runtimeOptions"]["frameworks"] as JArray; - - var versions = new RavenServerVersions(); - - foreach (var fw in frameworks.OfType()) - { - var name = fw["name"].Value(); - var versionString = fw["version"].Value(); - - if (Version.TryParse(versionString, out var version)) - { - if (name == "Microsoft.NETCore.App") - { - versions.NetRuntime = version; - } - else if (name == "Microsoft.AspNetCore.App") - { - versions.AspNetCore = version; - } - } - } - - return versions; - } - - class AipVersions - { - public Version NetRuntimeMin; - public Version NetRuntimeMax; - public Version AspNetCoreMin; - public string AspNetCoreRegistryKey; - - public Version NetRuntimeToInstall; - public Version AspNetCoreToInstall; - - public override string ToString() => $"AipVersions: NetRuntimeMin = {NetRuntimeMin}, NetRuntimeMax = {NetRuntimeMax}, AspNetCoreMin = {AspNetCoreMin}"; - } - - class RavenServerVersions - { - public Version NetRuntime; - public Version AspNetCore; - - public override string ToString() => $"RavenServerVersions: NetCoreApp = {NetRuntime}, AspNetCore = {AspNetCore}"; - } - } -} diff --git a/src/ServiceControlInstaller.Packaging/ServiceControlInstaller.Packaging.csproj b/src/ServiceControlInstaller.Packaging/ServiceControlInstaller.Packaging.csproj index d14e4ea6fa..d3f6f76d7d 100644 --- a/src/ServiceControlInstaller.Packaging/ServiceControlInstaller.Packaging.csproj +++ b/src/ServiceControlInstaller.Packaging/ServiceControlInstaller.Packaging.csproj @@ -20,4 +20,12 @@
+ + + ..\..\zip\ + + + + + diff --git a/src/Setup/Prerequisites/.NET Framework 4.7.2/NDP472-KB4054531-Web.exe b/src/Setup/Prerequisites/.NET Framework 4.7.2/NDP472-KB4054531-Web.exe deleted file mode 100644 index c7684d837e..0000000000 Binary files a/src/Setup/Prerequisites/.NET Framework 4.7.2/NDP472-KB4054531-Web.exe and /dev/null differ diff --git a/src/Setup/Res/License.rtf b/src/Setup/Res/License.rtf deleted file mode 100644 index ef4ff38b33..0000000000 --- a/src/Setup/Res/License.rtf +++ /dev/null @@ -1,376 +0,0 @@ -{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff31507\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi31507\deflang7177\deflangfe7177\themelang7177\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} -{\f37\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} -{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0302020204030204}Calibri Light;} -{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} -{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;} -{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f39\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f40\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\f42\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f43\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f44\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f45\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} -{\f46\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f47\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f39\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f40\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\f42\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f43\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f44\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f45\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} -{\f46\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f47\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f409\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\f410\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;} -{\f412\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f413\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f414\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\f415\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);} -{\f416\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\f417\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} -{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} -{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} -{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} -{\fhimajor\f31528\fbidi \fswiss\fcharset238\fprq2 Calibri Light CE;}{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;} -{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;}{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);} -{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;}{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} -{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} -{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} -{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} -{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} -{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} -{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;} -{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;} -{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;} -{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}} -{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0; -\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red67\green67\blue67;\red17\green85\blue204;}{\*\defchp \fs22\loch\af31506\hich\af31506\dbch\af31505 }{\*\defpap -\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 -\af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang7177\langfe7177\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp7177\langfenp7177 \snext0 \sqformat \spriority0 Normal;}{ -\s1\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af0\afs48\alang1025 \ltrch\fcs0 \b\fs48\lang7177\langfe7177\kerning36\cgrid\langnp7177\langfenp7177 -\sbasedon0 \snext1 \slink15 \sqformat \spriority9 \styrsid4271997 heading 1;}{\s2\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af0\afs36\alang1025 -\ltrch\fcs0 \b\fs36\lang7177\langfe7177\cgrid\langnp7177\langfenp7177 \sbasedon0 \snext2 \slink16 \sqformat \spriority9 \styrsid4271997 heading 2;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* -\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1 -\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang7177\langfe7177\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp7177\langfenp7177 \snext11 \ssemihidden \sunhideused -Normal Table;}{\*\cs15 \additive \rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 \b\f0\fs48\kerning36 \sbasedon10 \slink1 \slocked \spriority9 \styrsid4271997 Heading 1 Char;}{\*\cs16 \additive \rtlch\fcs1 \ab\af0\afs36 \ltrch\fcs0 \b\f0\fs36 -\sbasedon10 \slink2 \slocked \spriority9 \styrsid4271997 Heading 2 Char;}{\s17\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 -\fs24\lang7177\langfe7177\cgrid\langnp7177\langfenp7177 \sbasedon0 \snext17 \ssemihidden \sunhideused \styrsid4271997 Normal (Web);}{\*\cs18 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf2 \sbasedon10 \ssemihidden \sunhideused \styrsid4271997 Hyperlink;}} -{\*\listtable{\list\listtemplateid1095287588\listsimple{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelold\levelspace0\levelindent283{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\fbias0 } -{\listname ;}\listid132450878}{\list\listtemplateid-1124536876\listsimple{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelold\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af37 -\ltrch\fcs0 \f37\fbias0 }{\listname ;}\listid1208254014}{\list\listtemplateid-1876671870{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 -\ltrch\fcs0 \fi-360\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-360\li1440 -\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-360\li2160\jclisttab\tx2160\lin2160 } -{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc0 -\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc0\levelnfcn0\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 -{\leveltext\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid2021663013}}{\*\listoverridetable{\listoverride\listid1208254014\listoverridecount0\ls1}{\listoverride\listid132450878 -\listoverridecount0\ls2}{\listoverride\listid2021663013\listoverridecount0\ls3}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}{\*\rsidtbl \rsid4271997\rsid12078728}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0 -\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapRight1\mintLim0\mnaryLim1}{\info{\operator William Brander}{\creatim\yr2020\mo3\dy11\hr9\min24}{\revtim\yr2020\mo3\dy11\hr9\min25}{\version2}{\edmins1}{\nofpages5}{\nofwords2238}{\nofchars12762} -{\nofcharsws14971}{\vern57449}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect -\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont0\relyonvml0\donotembedlingdata1\grfdocevents0\validatexml0\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors0\horzdoc\dghspace120\dgvspace120\dghorigin1701 -\dgvorigin1984\dghshow0\dgvshow3\jcompress\viewkind1\viewscale100\rsidroot4271997 \nouicompat \fet0{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\sectdefaultcl\sftnbj {\*\pnseclvl1 -\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5 -\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang -{\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\qc \li0\ri0\sb240\sa240\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid4271997 \rtlch\fcs1 -\af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang7177\langfe7177\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp7177\langfenp7177 {\rtlch\fcs1 \ab\af0\afs40 \ltrch\fcs0 \b\f0\fs40\cf1\insrsid4271997\charrsid4271997 Non-Production License Agreement}{ -\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid4271997\charrsid4271997 -\par }\pard \ltrpar\qj \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid4271997 {\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 -IMPORTANT - READ CAREFULLY THE TERMS AND CONDITIONS OF THIS LICENSE AGREEMENT (THIS "}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\insrsid4271997\charrsid4271997 AGREEMENT}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 -") BEFORE CLICKING ON THE "I AGREE" BUTTON. BY CLICKING ON THE "I AGREE" BUTTON, INSTALLING OR USING ALL OR ANY PORTION OF THE SOFTWARE (AS DEFINED BELOW -), YOU, ON YOUR OWN BEHALF AS AN INDIVIDUAL, AND ON BEHALF OF YOUR EMPLOYER OR ANOTHER ENTITY WHICH HAS NOT YET DONE SO (COLLECTIVELY "}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\insrsid4271997\charrsid4271997 YOU}{\rtlch\fcs1 \af0 \ltrch\fcs0 -\f0\cf1\insrsid4271997\charrsid4271997 ", OR "}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\insrsid4271997\charrsid4271997 LICENSEE}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 -"), ACKNOWLEDGE THAT YOU HAVE READ THIS AGREEMENT, THAT YOU UNDERSTAND IT, AND ARE CONSENTING TO BE BOUND BY AND ARE BECOMING A PARTY TO THIS AGREEMENT WITH NSERVICEBUS LTD. DOING BUSINESS AS PARTICULAR SOFTWARE) ("}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 -\b\f0\cf1\insrsid4271997\charrsid4271997 NSB}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 "). IF YOU DO NOT AGREE TO THE TERMS OF THIS AGREEMENT, YOU MAY NOT DOWNLOAD, INSTALL OR USE NSB'S SOFTWARE AND RELATED DOCUMENTATION. IF YOU - WISH TO USE THE SOFTWARE AS AN EMPLOYEE, CONTRACTOR, OR AGENT OF A CORPORATION, PARTNERSHIP OR SIMILAR ENTITY, THEN YOU MUST BE AUTHORIZED TO SIGN FOR AND BIND THE ENTITY IN ORDER TO ACCEPT THE TERMS OF THIS AGREEMENT. THE LICENSE GRANTED UNDER THIS AGRE -E -MENT IS HEREBY CONDITIONED UPON ACCEPTANCE OF SUCH TERMS BY SUCH AUTHORIZED PERSONNEL. IF YOU HAVE ENTERED INTO A SEPARATE WRITTEN LICENSE AGREEMENT WITH NSB FOR USE OF THE SOFTWARE, THE TERMS AND CONDITIONS OF SUCH OTHER AGREEMENT SHALL PREVAIL OVER ANY -CONFLICTING TERMS OR CONDITIONS IN THIS AGREEMENT.}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid4271997\charrsid4271997 -\par }{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 -FURTHERMORE, YOU HEREBY WAIVE ANY RIGHTS OR REQUIREMENTS UNDER ANY LAWS OR REGULATIONS IN ANY JURISDICTION WHICH REQUIRE AN ORIGINAL (NON-ELECTRONIC) SIGNATURE OR DELIVERY OR RETENTION OF NON-ELECTRONIC REC -ORDS, TO THE EXTENT PERMITTED UNDER APPLICABLE LAW.}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid4271997\charrsid4271997 -\par }{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 YOU ASSUME ALL RESPONSIBILITY FOR THE SELECTION OF THE SOFTWARE TO ACHIEVE YOUR INTENDED RESULTS AND FOR THE INSTALLATION, USE AND RESULTS OBTAINED FROM THE SOFTWARE.}{\rtlch\fcs1 -\af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\kerning36\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 1.\tab}}\pard \ltrpar\qj \fi-360\li360\ri0\sb120\sa120\widctlpar -\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faroman\ls3\outlinelevel0\adjustright\rin0\lin360\itap0\pararsid4271997 {\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 Definitions}{\rtlch\fcs1 \af0 \ltrch\fcs0 -\f0\cf1\kerning36\insrsid4271997\charrsid4271997 .\~}{\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 \b\f0\fs48\cf1\kerning36\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 1.\tab}}\pard \ltrpar\qj \fi-360\li927\ri0\sb120\sa120\widctlpar -\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faroman\ls3\ilvl1\outlinelevel1\adjustright\rin0\lin927\itap0\pararsid4271997 {\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\insrsid4271997\charrsid4271997 "Initial Evaluation Period"}{\rtlch\fcs1 \af0 -\ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 means a period of fourteen}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\insrsid4271997\charrsid4271997 }{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 -(14) days commencing on the date Licensee installs or receives access to the Software.}{\rtlch\fcs1 \ab\af0\afs36 \ltrch\fcs0 \b\f0\fs36\cf1\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 2.\tab}}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\insrsid4271997\charrsid4271997 "Development}{\rtlch\fcs1 -\af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 }{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\insrsid4271997\charrsid4271997 Period"}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 - means a period of ninety (90) days commencing on the expiration of the Initial Evaluation Peri -od. The Development Period may be extended to include additional 90 day periods following Licensee's written request and NSB's approval of such additional periods.}{\rtlch\fcs1 \ab\af0\afs36 \ltrch\fcs0 \b\f0\fs36\cf1\insrsid4271997\charrsid4271997 - -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 3.\tab}}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\insrsid4271997\charrsid4271997 "Non-Production Period}{ -\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 " means the Initial Evaluation Period and the Development Period together\~}{\rtlch\fcs1 \ab\af0\afs36 \ltrch\fcs0 \b\f0\fs36\cf1\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af0\afs22 \ltrch\fcs0 \fs22\cf1\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 4.\tab}}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 \'93}{\rtlch\fcs1 \ab\af0 -\ltrch\fcs0 \b\f0\cf1\insrsid4271997\charrsid4271997 Production Use}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 \'94 - shall mean the use of the Software or its Derivative Works as part of an Integrated Product by any End User of Customer. Production Use shall not include use of the software by Customer\rquote s employees or Contractors (defined below) that -are using the Software solely for the purposes of Customer's development and testing of an Integrated Product.}{\rtlch\fcs1 \ab\af0\afs36 \ltrch\fcs0 \b\f0\fs36\cf1\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 5.\tab}}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\insrsid4271997\charrsid4271997 "Software" }{\rtlch\fcs1 -\af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 means the Particular Service Platform.}{\rtlch\fcs1 \ab\af0\afs36 \ltrch\fcs0 \b\f0\fs36\cf1\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\kerning36\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 2.\tab}}\pard \ltrpar\qj \fi-360\li360\ri0\sb120\sa120\widctlpar -\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faroman\ls3\outlinelevel0\adjustright\rin0\lin360\itap0\pararsid4271997 {\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 License Grant: }{\rtlch\fcs1 \af0 \ltrch\fcs0 -\f0\cf1\kerning36\insrsid4271997\charrsid4271997 During the Non-Production Period, subject to Licensee's compliance with the term -s of this Agreement, NSB hereby grants to Licensee a temporary, non-exclusive, revocable, non-sublicensable, non-transferable, limited license to: (i) during the Initial Evaluation, use the Software, solely to evaluate whether to license the Software from - -NSB or enter into some other commercial arrangement with NSB, (ii) during the Development Period, use the Software solely to develop, analyse and test in non-production environments. Licensee may not use the Software for general production use. The Softwa -re is licensed for Licensee\rquote s internal and non-commercial use, and neither the Software nor any derivative of the Software may be used by, sub-licensed, re-sold, transferred, rented or distributed to any other party.}{\rtlch\fcs1 \ab\af0\afs48 -\ltrch\fcs0 \b\f0\fs48\cf17\kerning36\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\kerning36\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 3.\tab}}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 -License Restrictions:}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 Licensee may not, -nor permit anyone else to, directly or indirectly: (i) distribute, rent, lease, market, sublicense, resell or otherwise transfer the Software or use the Software to operate, or make the Software available, in a time-sharing, outsourcing, or service bureau - -environment or otherwise make it available on a network accessible to multiple users or devices; (ii) copy or modify the Software either alone or in conjunction with any other product or program, or use the Software for the production of any product or pr -o -gram; (iii) remove, alter or obscure, any identification, including copyright, trademark, patent or other notices, contained in or on the Software; (iv) circumvent, disable or otherwise interfere with security-related features of the Software or features -t -hat prevent, restrict or limit use of the Software;(v) use the NSB's name, logo or trademarks without prior written consent from NSB; or (vi) use the Software to violate any applicable laws, rules or regulations, or for any unlawful, harmful, irresponsibl -e, or inappropriate purpose, or in any manner that breaches this Agreement.}{\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 \b\f0\fs48\cf17\kerning36\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\kerning36\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 4.\tab}}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 -Ownership Rights: }{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 -The Software under this license is not for sale and is NSB's sole property. Licensee acknowledges and agrees that all right, title, and interest in and to the Soft -ware, and any modifications, improvements, updates, upgrades or derivative works of, the Software, including associated intellectual property rights, are exclusively owned by, and shall remain solely with, NSB. This Agreement does not convey to Licensee a -ny ownership interest in the Software. Nothing herein constitutes a waiver of the Company\rquote -s intellectual property rights under any law. If NSB receives any feedback (e.g., questions, comments, suggestions or the like) regarding any of the Software (collectively, \'93Feedback\'94 -), Licensee hereby grants to NSB a non-exclusive, perpetual, irrevocable, worldwide, fully-paid and royalty-free license to such Feedback. It is further understood that use of Feedback, if any, may be made by NSB at its sole discretion, and - that NSB in no way shall be obliged to make use of any kind of the Feedback or part thereof. \line -Any anonymous information, which is derived from the use of the Software (i.e., metadata, aggregated and/or analytics information) and which is not personally identifiable information (\'93Analytics Information\'94) is NSB's exclusive property.}{ -\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 \b\f0\fs48\cf1\kerning36\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\kerning36\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 5.\tab}}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 -Term and Termination: }{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 This Agreement and Licensee\rquote -s license to use the Software will terminate upon expiration of the Non-Production Period, unless terminated earlier in accordan -ce with the terms of this Agreement. NSB may terminate this Agreement and the license granted to Licensee herein at any time and for any reason, without liability of any kind to Licensee in connection with the consequences of such termination. Unauthorize -d - copying of the Software or otherwise failing to comply with this Agreement will result in immediate termination of this Agreement and will make available to NSB any applicable legal remedies. Upon termination of the license, Licensee shall uninstall, cea -s -e using, and permanently delete the Software. Furthermore, upon termination of this Agreement (or during the term of the Agreement), you will notify NSB if you wish to enter a Subscription License Agreement with NSB for the use of the Software for the pro -duction of any product or program.\~\~\~}{\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 \b\f0\fs48\cf17\kerning36\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\kerning36\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 6.\tab}}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 -Non Disclosure: }{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 -The Software, including any information related thereto that is not part of the public domain, is owned solely by NSB and is considered confidential information. As such, Licensee confirms that it shall keep any information in connection with the Software - provided by or on behalf of NSB confidential and under access and use restrictions sufficient to protect copying, usage or disclosure thereof. Such restrictions will be at least as stringent as those applied to Licensee\rquote -s most valuable confidential and proprietary information. No information related to the Software shall be disclosed by Licensee\~ - to any third party, unless NSB has agreed to such disclosure in writing and in advance. Licensee acknowledges and confirms that the confidentiality obligations und}{\rtlch\fcs1 \af44 \ltrch\fcs0 \f44\cf1\kerning36\insrsid4271997\charrsid4271997 -er this Section \'fd6 shall survive the Period and continue to be in full force and effect after the termination of this Agreement.}{\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 \b\f0\fs48\cf17\kerning36\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\kerning36\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 7.\tab}}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 -Privacy Policy: }{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 NSB will collect and use information about your use of the Software in accordance with NSB\rquote s privacy policy which is available at} -{\field\fldedit{\*\fldinst {\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 \b\f0\fs48\cf1\kerning36\insrsid4271997\charrsid4271997 HYPERLINK "https://particular.net/privacy" }}{\fldrslt {\rtlch\fcs1 \af0 \ltrch\fcs0 -\f0\cf1\kerning36\insrsid4271997\charrsid4271997 }{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\ul\cf18\kerning36\insrsid4271997\charrsid4271997 https://particular.net/privacy}}}\sectd \ltrsect\linex0\sectdefaultcl\sftnbj {\rtlch\fcs1 \af0 \ltrch\fcs0 -\f0\cf1\kerning36\insrsid4271997\charrsid4271997 ("}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 Privacy Policy}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 -"), as may be amended from time to time, and by using the Software you agree that NSB may do so. Notwithstanding the foregoing, you are aware t -hat you are not legally obligated to provide us with personal information, and you hereby confirm that when you provide NSB with personal information, you do so of your own free will.}{\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 -\b\f0\fs48\cf1\kerning36\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\kerning36\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 8.\tab}}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 -Third-Party Software: }{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 Licensee acknowledges that portions of the Softwar -e include Third-Party Software (including open source software) that may be subject to the terms and conditions imposed by the licensors of the Third-Party Software ("}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 -Third-Party Terms}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 "). In order to comply with the Third Party Terms a list of the Third Party-Software and related Third-Party Terms is available at}{\field\fldedit{\*\fldinst -{\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 \b\f0\fs48\cf17\kerning36\insrsid4271997\charrsid4271997 HYPERLINK "http://particular.net/ThirdPartyLicenses" }}{\fldrslt {\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 }}} -\sectd \ltrsect\linex0\sectdefaultcl\sftnbj {\field\fldedit{\*\fldinst {\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 \b\f0\fs48\cf17\kerning36\insrsid4271997\charrsid4271997 HYPERLINK "https://particular.net/third-party-licenses" }}{\fldrslt {\rtlch\fcs1 \af0 -\ltrch\fcs0 \f0\ul\cf18\kerning36\insrsid4271997\charrsid4271997 https://particular.net/third-party-licenses}}}\sectd \ltrsect\linex0\sectdefaultcl\sftnbj {\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 -. Licensee agrees (i) that its use of - the Third-Party Software is subject to and governed by the Third-Party Terms; and (ii) to comply with all Third-Party Terms. To the extent of any conflict between any Third-Party Terms and the terms of this Agreement, the Third-Party Terms shall prevail -in connection with the related Third-Party Software. Notwithstanding anything to the contrary in this Agreement, NSB makes no warranty or indemnity with respect to any Third-Party Software.}{\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 -\b\f0\fs48\cf17\kerning36\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\kerning36\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 9.\tab}}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 -Exclusion of Warranty:}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 THE SOFTWARE IS PROVIDED TO LICENSEE AT NO C -HARGE ON AN "AS-IS" BASIS, AND TO THE MAXIMUM EXTENT PERMITTED BY LAW, NSB DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ALL WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMEN -T.}{\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 \b\f0\fs48\cf1\kerning36\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\kerning36\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 10.\tab}}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 -Liability: }{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 NOTWITHSTANDING ANYTHING TO THE CONTRARY, INCLUDING ANY PROVISION OF THIS AGREEMENT:\~}{\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 -\b\f0\fs48\cf1\kerning36\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af0\afs22 \ltrch\fcs0 \fs22\cf1\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 1.\tab}}\pard \ltrpar\qj \fi-360\li927\ri0\sb120\sa120\widctlpar -\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faroman\ls3\ilvl1\outlinelevel1\adjustright\rin0\lin927\itap0\pararsid4271997 {\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 -IN NO EVENT SHALL NSB BE LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, INDIRECT, CONSEQUENTIAL, SPECIAL, EXEMPLARY, PUNITIVE OR INCIDENTAL DAMAGES, OR DAMAGES FOR LOSS OF BUSINESS OR PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INF -ORMATION, LOSS OF GOODWILL OR OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE;}{\rtlch\fcs1 \ab\af0\afs36 \ltrch\fcs0 \b\f0\fs36\cf1\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af0\afs22 \ltrch\fcs0 \fs22\cf1\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 2.\tab}}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 -THE TOTAL CUMULATIVE LIABILITY OF NSB FOR ALL DAMAGES AND LOSSES THAT ARISE UNDER OR IN CONNECTION WITH THIS AGREEMENT, OR THAT RESULT FROM YOUR -USE OF OR INABILITY TO USE THE SOFTWARE, SHALL NOT IN ANY CIRCUMSTANCES EXCEED THE AMOUNT OF FEES OR OTHER PAYMENTS, IF ANY, ACTUALLY PAID BY YOU TO NSB DURING THE NON- PRODUCTION PERIOD FOR THE USE OF THE SOFTWARE PURSUANT TO THE LICENSE GRANTED HEREIN;} -{\rtlch\fcs1 \ab\af0\afs36 \ltrch\fcs0 \b\f0\fs36\cf1\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af0\afs22 \ltrch\fcs0 \fs22\cf1\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 3.\tab}}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 T}{\rtlch\fcs1 \af44 \ltrch\fcs0 -\f44\cf1\insrsid4271997\charrsid4271997 HE LIMITATIONS ON NSB\rquote S LIABILITY SET FORTH IN THIS SECTION \'fd10 \'fd\'fd\'fd -SHALL APPLY EVEN IF NSB HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND REGARDLESS OF THE THEORY OR BASIS OF LIABILITY (INCLUDING, WITHOUT LIMITATION, BREACH OF CONTRACT, TORT, NEGL}{\rtlch\fcs1 \af0 \ltrch\fcs0 -\f0\cf1\insrsid4271997\charrsid4271997 IGENCE OR STRICT LIABILITY). SOME STATES MAY NOT ALLOW THE LIMITATION OR EXCLUSION OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE ABOVE LIMITATION OR EXCLUSION MAY NOT APPLY TO LICENSEE.}{\rtlch\fcs1 -\ab\af0\afs36 \ltrch\fcs0 \b\f0\fs36\cf1\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\kerning36\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 11.\tab}}\pard \ltrpar\qj \fi-360\li360\ri0\sb120\sa120\widctlpar -\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faroman\ls3\outlinelevel0\adjustright\rin0\lin360\itap0\pararsid4271997 {\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 Indemnification:}{\rtlch\fcs1 \af0 \ltrch\fcs0 -\f0\cf1\kerning36\insrsid4271997\charrsid4271997 You agree to defend, indemnify and hol -d harmless NSB and our affiliates, and our respective officers, directors, employees and agents, from and against any and all claims, damages, obligations, losses, liabilities, costs and expenses (including but not limited to attorney's fees) arising from -: (i) your use of, or inability to use, the Software; (ii) your violation of this Agreement; and (iii) your violation of any third party right, including without limitation any copyright, property, or privacy right.\~ - Without limiting or excusing your obliga}{\rtlch\fcs1 \af44 \ltrch\fcs0 \f44\cf1\kerning36\insrsid4271997\charrsid4271997 tions under this Section \'fd\'fd\'fd\'fd -11, NSB reserves the right, but is not under any obligation, to defense and control to assume the exclusive (at your expense) of any matter which is subject to an indemnification by you if you choose not to defend or settle it.}{\rtlch\fcs1 \af0 -\ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 You agree not to settle any matter subject to an indemnification by you without first obtaining our express approval.}{\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 -\b\f0\fs48\cf1\kerning36\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\kerning36\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 12.\tab}}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 -Government Use: }{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 If Licensee is part of an agency, department, or other entity of the United States Government ("}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 -\b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 Government}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 "), the use, d -uplication, reproduction, release, modification, disclosure or transfer of the Software is restricted in accordance with the Federal Acquisition Regulations as applied to civilian agencies and the Defense Federal Acquisition Regulation Supplement as appli -e -d to military agencies. The Software is a "commercial item", "commercial computer software" and "commercial computer software documentation". In accordance with such provisions, any use of the Software by the Government shall be governed solely by the ter -ms of this Agreement.}{\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 \b\f0\fs48\cf1\kerning36\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\kerning36\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 13.\tab}}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 -Governing Law/Jurisdiction:}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 - This Agreement shall be deemed to be made in, and the validity, interpretation and enforcement of this Agreement, and any disputes between you and NSB, shall be governed by and construed in accordance with t -he laws of, the State of New York, NY, excluding its conflicts of laws principles and excluding the United Nations Convention on Contracts for the International Sale of Goods. All disputes arising out of or relating to this Agreement shall be submitted to - -the exclusive jurisdiction of the courts of competent jurisdiction located in the state of New York, NY, and each party irrevocably consents to such personal jurisdiction and waives all objections to such venue. Notwithstanding the foregoing, NSB may seek - injunctive relief in any court of competent jurisdiction worldwide.}{\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 \b\f0\fs48\cf1\kerning36\insrsid4271997\charrsid4271997 -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \ab\af0\afs22 \ltrch\fcs0 \b\fs22\cf1\kerning36\dbch\af0\insrsid4271997\charrsid4271997 \hich\af0\dbch\af0\loch\f0 14.\tab}}{\rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\f0\cf1\kerning36\insrsid4271997\charrsid4271997 -Miscellaneous:}{\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\kerning36\insrsid4271997\charrsid4271997 - This Agreement represents the complete agreement concerning the Software between NSB and Licensee and supersedes all prior agreements and representations between NSB and Lic -ensee. If any provision of this Agreement is held to be unenforceable for any reason, such provision shall be reformed only to the extent necessary to make it enforceable. Any waiver of any provision of this Agreement will be effective only if in writing -a -nd signed by NSB. The terms of this Agreement are personal to Licensee and may not be assigned or transferred for any reason whatsoever without the prior written consent of NSB and any action or conduct in violation of the foregoing shall be void and with -o}{\rtlch\fcs1 \af44 \ltrch\fcs0 \f44\cf1\kerning36\insrsid4271997\charrsid4271997 ut effect. NSB may assign this Agreement and delegate any of its obligations hereunder without restriction. This Section \'fd14 and Sections \'fd3 (License Restrictions), -\'fd4 (Ownership Rights), \'fd6 (Non-Disclosure), \'fd8 (Third Party Software), \'fd9 (Exclusion of Warranty) \'fd10 (Liability), \'fd12 (Government Use), and \'fd -13 (Governing Law and Jurisdiction), and any provision so intended, and any outstanding obligations and rights previously accrued, shall survive termination or expiration of this Agreement.}{\rtlch\fcs1 \ab\af0\afs48 \ltrch\fcs0 -\b\f0\fs48\cf1\kerning36\insrsid4271997\charrsid4271997 -\par }\pard \ltrpar\qj \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid4271997 {\rtlch\fcs1 \af0 \ltrch\fcs0 \f0\cf1\insrsid4271997\charrsid4271997 YOU EXPRESSLY A -CKNOWLEDGE THAT YOU HAVE READ THESE TERMS AND UNDERSTAND THE RIGHTS, OBLIGATIONS, TERMS AND CONDITIONS SET FORTH HEREIN. BY CLICKING ON THE AGREE BUTTON AND/OR CONTINUING TO INSTALL OR USE THE SOFTWARE, YOU EXPRESSLY CONSENT TO BE BOUND BY THESE TERMS.}{ -\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid4271997\charrsid4271997 -\par }\pard \ltrpar\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid4271997 {\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid12078728\charrsid4271997 -\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a -9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad -5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 -b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 -0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 -a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f -c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 -0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 -a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 -6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b -4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b -4757e8d3f729e245eb2b260a0238fd010000ffff0300504b030414000600080000002100aa5225dfc60600008b1a0000160000007468656d652f7468656d652f -7468656d65312e786d6cec595d8bdb46147d2ff43f08bd3bfe92fcb1c41b6cd9ceb6d94d42eca4e4716c8fadc98e344633de8d0981923c160aa569e943037deb -43691b48a02fe9afd936a54d217fa17746b63c638fbb9b2585a5640d8b343af7ce997bafce1d4997afdc8fa87384134e58dc708b970aae83e3211b9178d2706f -f7bbb99aeb7081e211a22cc60d778eb97b65f7c30f2ea31d11e2083b601ff31dd4704321a63bf93c1fc230e297d814c7706dcc920809384d26f951828ec16f44 -f3a542a1928f10895d274611b8bd311e932176fad2a5bbbb74dea1701a0b2e078634e949d7d8b050d8d1615122f89c0734718e106db830cf881df7f17de13a14 -7101171a6e41fdb9f9ddcb79b4b330a2628bad66d7557f0bbb85c1e8b0a4e64c26836c52cff3bd4a33f3af00546ce23ad54ea553c9fc29001a0e61a52917dda7 -dfaab7dafe02ab81d2438bef76b55d2e1a78cd7f798373d3973f03af40a97f6f03dfed06104503af4029dedfc07b5eb51478065e81527c65035f2d34db5ed5c0 -2b5048497cb8812ef89572b05c6d061933ba6785d77daf5b2d2d9caf50500d5975c929c62c16db6a2d42f758d2058004522448ec88f9148fd110aa3840940c12 -e2ec93490885374531e3305c2815ba8532fc973f4f1da988a01d8c346bc90b98f08d21c9c7e1c3844c45c3fd18bcba1ae4cdcb1fdfbc7cee9c3c7a71f2e89793 -c78f4f1efd9c3a32acf6503cd1ad5e7fffc5df4f3f75fe7afeddeb275fd9f15cc7fffed367bffdfaa51d082b5d85e0d5d7cffe78f1ecd5379ffff9c3130bbc99 -a0810eef930873e73a3e766eb10816a6426032c783e4ed2cfa2122ba45339e701423398bc57f478406fafa1c5164c1b5b019c13b09488c0d787576cf20dc0b93 -9920168fd7c2c8001e30465b2cb146e19a9c4b0b737f164fec9327331d770ba123dbdc018a8dfc766653d05662731984d8a07993a258a0098eb170e4357688b1 -6575770931e27a408609e36c2c9cbbc46921620d499f0c8c6a5a19ed9108f232b711847c1bb139b8e3b418b5adba8d8f4c24dc15885ac8f73135c27815cd048a -6c2efb28a27ac0f791086d247bf364a8e33a5c40a6279832a733c29cdb6c6e24b05e2de9d7405eec693fa0f3c84426821cda7cee23c674649b1d06218aa6366c -8fc4a18efd881f428922e7261336f80133ef10790e7940f1d674df21d848f7e96a701b9455a7b42a107965965872791533a37e7b733a4658490d08bfa1e71189 -4f15f73559f7ff5b5907217df5ed53cbaa2eaaa0371362bda3f6d6647c1b6e5dbc03968cc8c5d7ee369ac53731dc2e9b0decbd74bf976ef77f2fdddbeee7772f -d82b8d06f9965bc574abae36eed1d67dfb9850da13738af7b9daba73e84ca32e0c4a3bf5cc8ab3e7b8690887f24e86090cdc2441cac64998f88488b017a229ec -ef8bae7432e10bd713ee4c19876dbf1ab6fa96783a8b0ed8287d5c2d16e5a3692a1e1c89d578c1cfc6e15143a4e84a75f50896b9576c27ea51794940dabe0d09 -6d329344d942a2ba1c9441520fe610340b09b5b277c2a26e615193ee97a9da6001d4b2acc0d6c9810d57c3f53d30012378a242148f649ed2542fb3ab92f92e33 -bd2d984605c03e625901ab4cd725d7adcb93ab4b4bed0c99364868e566925091513d8c87688417d52947cf42e36d735d5fa5d4a02743a1e683d25ad1a8d6fe8d -c579730d76ebda40635d2968ec1c37dc4ad9879219a269c31dc3633f1c4653a81d2eb7bc884ee0ddd95024e90d7f1e6599265cb4110fd3802bd149d520220227 -0e2551c395cbcfd24063a5218a5bb104827061c9d541562e1a3948ba99643c1ee3a1d0d3ae8dc848a7a7a0f0a95658af2af3f383a5259b41ba7be1e8d819d059 -720b4189f9d5a20ce0887078fb534ca33922f03a3313b255fdad35a685eceaef13550da5e3884e43b4e828ba98a77025e5191d7596c5403b5bac1902aa8564d1 -080713d960f5a01add34eb1a2987ad5df7742319394d34573dd35015d935ed2a66ccb06c036bb13c5f93d7582d430c9aa677f854bad725b7bed4bab57d42d625 -20e059fc2c5df70c0d41a3b69acca026196fcab0d4ecc5a8d93b960b3c85da599a84a6fa95a5dbb5b8653dc23a1d0c9eabf383dd7ad5c2d078b9af549156df3d -f44f136c700fc4a30d2f81675470954af8f09020d810f5d49e24950db845ee8bc5ad0147ce2c210df741c16f7a41c90f72859adfc97965af90abf9cd72aee9fb -e562c72f16daadd243682c228c8a7efacda50bafa2e87cf1e5458d6f7c7d89966fdb2e0d599467eaeb4a5e11575f5f8aa5ed5f5f1c02a2f3a052ead6cbf55625 -572f37bb39afddaae5ea41a5956b57826abbdb0efc5abdfbd0758e14d86b9603afd2a9e52ac520c8799582a45fabe7aa5ea9d4f4aacd5ac76b3e5c6c6360e5a9 -7c2c6201e155bc76ff010000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468656d652f7468656d652f5f72656c732f -7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4350d363f2451eced0dae2c082e8761be -9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d262452282e3198720e274a939cd08a54f980 -ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe514173d9850528a2c6cce0239baa4c04ca5b -babac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c0200001300000000000000000000000000000000005b436f6e74656e -745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b00000000000000000000000000300100005f72656c732f -2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c00000000000000000000000000190200007468656d652f7468656d652f74 -68656d654d616e616765722e786d6c504b01022d0014000600080000002100aa5225dfc60600008b1a00001600000000000000000000000000d6020000746865 -6d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b0100002700000000000000000000000000d00900007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000cb0a00000000} -{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d -617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 -6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 -656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} -{\*\latentstyles\lsdstimax371\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong; -\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Table;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 1; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 2; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 2; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 2; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 6; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 2; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 6; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 2; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Contemporary;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Elegant;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Professional; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 2; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Theme;\lsdsemihidden1 \lsdlocked0 Placeholder Text; -\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2; -\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List; -\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;\lsdpriority61 \lsdlocked0 Light List Accent 1; -\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdsemihidden1 \lsdlocked0 Revision; -\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1; -\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;\lsdpriority72 \lsdlocked0 Colorful List Accent 1; -\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2; -\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2; -\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2; -\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3; -\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3; -\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4; -\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4; -\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4; -\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5; -\lsdpriority62 \lsdlocked0 Light Grid Accent 5;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; -\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; -\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; -\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; -\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; -\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; -\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; -\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; -\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; -\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; -\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; -\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; -\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; -\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; -\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; -\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; -\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; -\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; -\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; -\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; -\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; -\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; -\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; -\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; -\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; -\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; -\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; -\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;}}{\*\datastore 010500000200000018000000 -4d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 -d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000b076 -863a76f7d501feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 -00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 -000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file diff --git a/src/Setup/Res/Operations-logo.png b/src/Setup/Res/Operations-logo.png deleted file mode 100644 index 7c9a1469ad..0000000000 Binary files a/src/Setup/Res/Operations-logo.png and /dev/null differ diff --git a/src/Setup/Res/Operations.ico b/src/Setup/Res/Operations.ico deleted file mode 100644 index 6b301464eb..0000000000 Binary files a/src/Setup/Res/Operations.ico and /dev/null differ diff --git a/src/Setup/Res/banner.jpg b/src/Setup/Res/banner.jpg deleted file mode 100644 index e75d22204b..0000000000 Binary files a/src/Setup/Res/banner.jpg and /dev/null differ diff --git a/src/Setup/Res/dialog.jpg b/src/Setup/Res/dialog.jpg deleted file mode 100644 index e1f8cd8991..0000000000 Binary files a/src/Setup/Res/dialog.jpg and /dev/null differ diff --git a/src/Setup/Res/exclamation.ico b/src/Setup/Res/exclamation.ico deleted file mode 100644 index fab166aa57..0000000000 Binary files a/src/Setup/Res/exclamation.ico and /dev/null differ diff --git a/src/Setup/ServiceControl.aip b/src/Setup/ServiceControl.aip deleted file mode 100644 index eb74e61a4a..0000000000 --- a/src/Setup/ServiceControl.aip +++ /dev/null @@ -1,514 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Setup/Setup.csproj b/src/Setup/Setup.csproj deleted file mode 100644 index 4e0d590b31..0000000000 --- a/src/Setup/Setup.csproj +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - net472 - - - - - - - - - - - - - $(registry:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Caphyon\Advanced Installer@Advanced Installer Path) - "$(AdvancedInstallerPath)bin\x86\AdvancedInstaller.com" - ServiceControl.aip - commands.aic - - - - - - - $([System.IO.Path]::GetFullPath($(MSBuildProjectDirectory)\..\..\)) - $(RepoRootDir)assets\ - Particular.ServiceControl-$(MinVerVersion).exe - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Setup/commands.aic b/src/Setup/commands.aic deleted file mode 100644 index 7f85212ede..0000000000 --- a/src/Setup/commands.aic +++ /dev/null @@ -1,3 +0,0 @@ -;aic -SetAzureKeyVaultSecret AZURE_KEY_VAULT_CLIENT_SECRET -secret_is_env_var_name -Rebuild \ No newline at end of file diff --git a/src/TestHelper/TestHelper.csproj b/src/TestHelper/TestHelper.csproj index 6389585b6f..4dbfbf78d3 100644 --- a/src/TestHelper/TestHelper.csproj +++ b/src/TestHelper/TestHelper.csproj @@ -1,7 +1,7 @@ - net472 + net472;net7.0