From 20f0dac6b212895475a38b9b0be246d5edaf415c Mon Sep 17 00:00:00 2001 From: Livar Cunha Date: Tue, 25 Jul 2017 22:30:36 -0700 Subject: [PATCH 1/5] Making a change that will cause the first run notice to always show up in the first run of the CLI, even when it is installed by native installers. --- .../DotnetFirstTimeUseConfigurer.cs | 14 ++-- .../NoOpFirstTimeUseNoticeSentinel.cs | 26 +++++++ .../NuGetCacheSentinel.cs | 2 +- src/dotnet/Program.cs | 21 +++++- .../GivenADotnetFirstTimeUseConfigurer.cs | 68 +++++++++++++++++++ .../GivenANuGetCacheSentinel.cs | 12 ++++ ...atTheUserIsRunningDotNetForTheFirstTime.cs | 61 ++++++++++++++++- 7 files changed, 191 insertions(+), 13 deletions(-) create mode 100644 src/Microsoft.DotNet.Configurer/NoOpFirstTimeUseNoticeSentinel.cs diff --git a/src/Microsoft.DotNet.Configurer/DotnetFirstTimeUseConfigurer.cs b/src/Microsoft.DotNet.Configurer/DotnetFirstTimeUseConfigurer.cs index e13637c47..9a89bcb6f 100644 --- a/src/Microsoft.DotNet.Configurer/DotnetFirstTimeUseConfigurer.cs +++ b/src/Microsoft.DotNet.Configurer/DotnetFirstTimeUseConfigurer.cs @@ -45,10 +45,12 @@ namespace Microsoft.DotNet.Configurer { PrintUnauthorizedAccessMessage(); } + else + { + PrintNugetCachePrimeMessage(); - PrintNugetCachePrimeMessage(); - - _nugetCachePrimer.PrimeCache(); + _nugetCachePrimer.PrimeCache(); + } } } @@ -81,6 +83,8 @@ namespace Microsoft.DotNet.Configurer private bool ShouldPrimeNugetCache() { return ShouldRunFirstRunExperience() && + !_nugetCacheSentinel.Exists() && + !_nugetCacheSentinel.InProgressSentinelAlreadyExists() && !_nugetCachePrimer.SkipPrimingTheCache(); } @@ -96,9 +100,7 @@ namespace Microsoft.DotNet.Configurer var skipFirstTimeExperience = _environmentProvider.GetEnvironmentVariableAsBool("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", false); - return !skipFirstTimeExperience && - !_nugetCacheSentinel.Exists() && - !_nugetCacheSentinel.InProgressSentinelAlreadyExists(); + return !skipFirstTimeExperience; } } } diff --git a/src/Microsoft.DotNet.Configurer/NoOpFirstTimeUseNoticeSentinel.cs b/src/Microsoft.DotNet.Configurer/NoOpFirstTimeUseNoticeSentinel.cs new file mode 100644 index 000000000..a801d4ee6 --- /dev/null +++ b/src/Microsoft.DotNet.Configurer/NoOpFirstTimeUseNoticeSentinel.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.EnvironmentAbstractions; +using NuGet.Configuration; + +namespace Microsoft.DotNet.Configurer +{ + public class NoOpFirstTimeUseNoticeSentinel : IFirstTimeUseNoticeSentinel + { + public bool Exists() + { + return true; + } + + public void CreateIfNotExists() + { + } + + public void Dispose() + { + } + } +} diff --git a/src/Microsoft.DotNet.Configurer/NuGetCacheSentinel.cs b/src/Microsoft.DotNet.Configurer/NuGetCacheSentinel.cs index def087615..bfc7a29d9 100644 --- a/src/Microsoft.DotNet.Configurer/NuGetCacheSentinel.cs +++ b/src/Microsoft.DotNet.Configurer/NuGetCacheSentinel.cs @@ -45,7 +45,7 @@ namespace Microsoft.DotNet.Configurer public bool InProgressSentinelAlreadyExists() { - return CouldNotGetAHandleToTheInProgressSentinel(); + return CouldNotGetAHandleToTheInProgressSentinel() && !UnauthorizedAccess; } public bool Exists() diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index 4611ec7b7..5f44524d4 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -81,8 +81,10 @@ namespace Microsoft.DotNet.Cli var lastArg = 0; var cliFallbackFolderPathCalculator = new CliFallbackFolderPathCalculator(); using (INuGetCacheSentinel nugetCacheSentinel = new NuGetCacheSentinel(cliFallbackFolderPathCalculator)) - using (IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = new FirstTimeUseNoticeSentinel(cliFallbackFolderPathCalculator)) + using (IFirstTimeUseNoticeSentinel disposableFirstTimeUseNoticeSentinel = + new FirstTimeUseNoticeSentinel(cliFallbackFolderPathCalculator)) { + IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = disposableFirstTimeUseNoticeSentinel; for (; lastArg < args.Length; lastArg++) { if (IsArg(args[lastArg], "d", "diagnostics")) @@ -113,10 +115,19 @@ namespace Microsoft.DotNet.Cli } else { - ConfigureDotNetForFirstTimeUse(nugetCacheSentinel, firstTimeUseNoticeSentinel, cliFallbackFolderPathCalculator); - // It's the command, and we're done! command = args[lastArg]; + + if (IsDotnetBeingInvokedFromNativeInstaller(command)) + { + firstTimeUseNoticeSentinel = new NoOpFirstTimeUseNoticeSentinel(); + } + + ConfigureDotNetForFirstTimeUse( + nugetCacheSentinel, + firstTimeUseNoticeSentinel, + cliFallbackFolderPathCalculator); + break; } } @@ -164,7 +175,11 @@ namespace Microsoft.DotNet.Cli } return exitCode; + } + private static bool IsDotnetBeingInvokedFromNativeInstaller(string command) + { + return command == "internal-reportinstallsuccess"; } private static void ConfigureDotNetForFirstTimeUse( diff --git a/test/Microsoft.DotNet.Configurer.UnitTests/GivenADotnetFirstTimeUseConfigurer.cs b/test/Microsoft.DotNet.Configurer.UnitTests/GivenADotnetFirstTimeUseConfigurer.cs index 24ed0a67e..a9a0e904b 100644 --- a/test/Microsoft.DotNet.Configurer.UnitTests/GivenADotnetFirstTimeUseConfigurer.cs +++ b/test/Microsoft.DotNet.Configurer.UnitTests/GivenADotnetFirstTimeUseConfigurer.cs @@ -233,5 +233,73 @@ namespace Microsoft.DotNet.Configurer.UnitTests _nugetCachePrimerMock.Verify(r => r.PrimeCache(), Times.Once); _reporterMock.Verify(r => r.Write(It.IsAny()), Times.Never); } + + [Fact] + public void It_prints_the_first_time_use_notice_if_the_cache_sentinel_exists_but_the_first_notice_sentinel_does_not() + { + _nugetCacheSentinelMock.Setup(n => n.Exists()).Returns(true); + _firstTimeUseNoticeSentinelMock.Setup(n => n.Exists()).Returns(false); + + var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer( + _nugetCachePrimerMock.Object, + _nugetCacheSentinelMock.Object, + _firstTimeUseNoticeSentinelMock.Object, + _environmentProviderMock.Object, + _reporterMock.Object, + CliFallbackFolderPath); + + dotnetFirstTimeUseConfigurer.Configure(); + + _reporterMock.Verify(r => + r.WriteLine(It.Is(str => str == LocalizableStrings.FirstTimeWelcomeMessage))); + _reporterMock.Verify( + r => r.WriteLine(It.Is(str => str == LocalizableStrings.NugetCachePrimeMessage)), + Times.Never); + _reporterMock.Verify(r => r.Write(It.IsAny()), Times.Never); + } + + [Fact] + public void It_prints_the_unauthorized_notice_if_the_cache_sentinel_reports_Unauthorized() + { + _nugetCacheSentinelMock.Setup(n => n.UnauthorizedAccess).Returns(true); + + var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer( + _nugetCachePrimerMock.Object, + _nugetCacheSentinelMock.Object, + _firstTimeUseNoticeSentinelMock.Object, + _environmentProviderMock.Object, + _reporterMock.Object, + CliFallbackFolderPath); + + dotnetFirstTimeUseConfigurer.Configure(); + + _reporterMock.Verify(r => + r.WriteLine(It.Is(str => str == LocalizableStrings.FirstTimeWelcomeMessage))); + _reporterMock.Verify(r => + r.WriteLine(It.Is(str => + str == string.Format(LocalizableStrings.UnauthorizedAccessMessage, CliFallbackFolderPath)))); + _reporterMock.Verify( + r => r.WriteLine(It.Is(str => str == LocalizableStrings.NugetCachePrimeMessage)), + Times.Never); + _reporterMock.Verify(r => r.Write(It.IsAny()), Times.Never); + } + + [Fact] + public void It_does_not_prime_the_cache_if_the_cache_sentinel_reports_Unauthorized() + { + _nugetCacheSentinelMock.Setup(n => n.UnauthorizedAccess).Returns(true); + + var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer( + _nugetCachePrimerMock.Object, + _nugetCacheSentinelMock.Object, + _firstTimeUseNoticeSentinelMock.Object, + _environmentProviderMock.Object, + _reporterMock.Object, + CliFallbackFolderPath); + + dotnetFirstTimeUseConfigurer.Configure(); + + _nugetCachePrimerMock.Verify(r => r.PrimeCache(), Times.Never); + } } } diff --git a/test/Microsoft.DotNet.Configurer.UnitTests/GivenANuGetCacheSentinel.cs b/test/Microsoft.DotNet.Configurer.UnitTests/GivenANuGetCacheSentinel.cs index 97397d79d..23ae45c4a 100644 --- a/test/Microsoft.DotNet.Configurer.UnitTests/GivenANuGetCacheSentinel.cs +++ b/test/Microsoft.DotNet.Configurer.UnitTests/GivenANuGetCacheSentinel.cs @@ -70,6 +70,18 @@ namespace Microsoft.DotNet.Configurer.UnitTests nugetCacheSentinel.InProgressSentinelAlreadyExists().Should().BeTrue(); } + [Fact] + public void It_returns_false_to_the_in_progress_sentinel_already_exists_when_it_fails_to_get_a_handle_to_it_but_it_failed_because_it_was_unauthorized() + { + var fileMock = new FileMock(); + var directoryMock = new DirectoryMock(); + fileMock.InProgressSentinel = null; + var nugetCacheSentinel = + new NuGetCacheSentinel(NUGET_CACHE_PATH, fileMock, directoryMock); + + nugetCacheSentinel.InProgressSentinelAlreadyExists().Should().BeFalse(); + } + [Fact] public void It_returns_false_to_the_in_progress_sentinel_already_exists_when_it_succeeds_in_getting_a_handle_to_it() { diff --git a/test/dotnet.Tests/GivenThatTheUserIsRunningDotNetForTheFirstTime.cs b/test/dotnet.Tests/GivenThatTheUserIsRunningDotNetForTheFirstTime.cs index 026053261..6fd6e0dfe 100644 --- a/test/dotnet.Tests/GivenThatTheUserIsRunningDotNetForTheFirstTime.cs +++ b/test/dotnet.Tests/GivenThatTheUserIsRunningDotNetForTheFirstTime.cs @@ -20,15 +20,17 @@ namespace Microsoft.DotNet.Tests private static CommandResult _firstDotnetNonVerbUseCommandResult; private static CommandResult _firstDotnetVerbUseCommandResult; private static DirectoryInfo _nugetFallbackFolder; + private static DirectoryInfo _dotDotnetFolder; + private static string _testDirectory; static GivenThatTheUserIsRunningDotNetForTheFirstTime() { - var testDirectory = TestAssets.CreateTestDirectory("Dotnet_first_time_experience_tests"); - var testNuGetHome = Path.Combine(testDirectory.FullName, "nuget_home"); + _testDirectory = TestAssets.CreateTestDirectory("Dotnet_first_time_experience_tests").FullName; + var testNuGetHome = Path.Combine(_testDirectory, "nuget_home"); var cliTestFallbackFolder = Path.Combine(testNuGetHome, ".dotnet", "NuGetFallbackFolder"); var command = new DotnetCommand() - .WithWorkingDirectory(testDirectory); + .WithWorkingDirectory(_testDirectory); command.Environment["HOME"] = testNuGetHome; command.Environment["USERPROFILE"] = testNuGetHome; command.Environment["APPDATA"] = testNuGetHome; @@ -40,6 +42,7 @@ namespace Microsoft.DotNet.Tests _firstDotnetVerbUseCommandResult = command.ExecuteWithCapturedOutput("new --debug:ephemeral-hive"); _nugetFallbackFolder = new DirectoryInfo(cliTestFallbackFolder); + _dotDotnetFolder = new DirectoryInfo(Path.Combine(testNuGetHome, ".dotnet")); } [Fact] @@ -77,6 +80,58 @@ namespace Microsoft.DotNet.Tests .HaveFile($"{GetDotnetVersion()}.dotnetSentinel"); } + [Fact] + public void ItCreatesAFirstUseSentinelFileUnderTheDotDotNetFolder() + { + _dotDotnetFolder + .Should() + .HaveFile($"{GetDotnetVersion()}.dotnetFirstUseSentinel"); + } + + [Fact] + public void ItDoesNotCreateAFirstUseSentinelFileUnderTheDotDotNetFolderWhenInternalReportInstallSuccessIsInvoked() + { + var newHome = Path.Combine(_testDirectory, "new_home"); + var newHomeFolder = new DirectoryInfo(Path.Combine(newHome, ".dotnet")); + + var command = new DotnetCommand() + .WithWorkingDirectory(_testDirectory); + command.Environment["HOME"] = newHome; + command.Environment["USERPROFILE"] = newHome; + command.Environment["APPDATA"] = newHome; + command.Environment["DOTNET_CLI_TEST_FALLBACKFOLDER"] = _nugetFallbackFolder.FullName; + command.Environment["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = ""; + command.Environment["SkipInvalidConfigurations"] = "true"; + + command.ExecuteWithCapturedOutput("internal-reportinstallsuccess test").Should().Pass(); + + newHomeFolder.Should().NotHaveFile($"{GetDotnetVersion()}.dotnetFirstUseSentinel"); + } + + [Fact] + public void ItShowsTheTelemetryNoticeWhenInvokingACommandAfterInternalReportInstallSuccessHasBeenInvoked() + { + var newHome = Path.Combine(_testDirectory, "new_home"); + var newHomeFolder = new DirectoryInfo(Path.Combine(newHome, ".dotnet")); + + var command = new DotnetCommand() + .WithWorkingDirectory(_testDirectory); + command.Environment["HOME"] = newHome; + command.Environment["USERPROFILE"] = newHome; + command.Environment["APPDATA"] = newHome; + command.Environment["DOTNET_CLI_TEST_FALLBACKFOLDER"] = _nugetFallbackFolder.FullName; + command.Environment["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = ""; + command.Environment["SkipInvalidConfigurations"] = "true"; + + command.ExecuteWithCapturedOutput("internal-reportinstallsuccess test").Should().Pass(); + + var result = command.ExecuteWithCapturedOutput("new --debug:ephemeral-hive"); + + result.StdOut + .Should() + .ContainVisuallySameFragment(Configurer.LocalizableStrings.FirstTimeWelcomeMessage); + } + [Fact] public void ItRestoresTheNuGetPackagesToTheNuGetCacheFolder() { From 015af46c997cad09dff609b9a87e44b09e974f8a Mon Sep 17 00:00:00 2001 From: Livar Cunha Date: Wed, 26 Jul 2017 10:29:10 -0700 Subject: [PATCH 2/5] Fixing a test that fails due to a race condition, because the .dotnet folder might not have been created yet. --- ...venThatTheUserIsRunningDotNetForTheFirstTime.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/dotnet.Tests/GivenThatTheUserIsRunningDotNetForTheFirstTime.cs b/test/dotnet.Tests/GivenThatTheUserIsRunningDotNetForTheFirstTime.cs index 6fd6e0dfe..f323a7d8d 100644 --- a/test/dotnet.Tests/GivenThatTheUserIsRunningDotNetForTheFirstTime.cs +++ b/test/dotnet.Tests/GivenThatTheUserIsRunningDotNetForTheFirstTime.cs @@ -91,21 +91,23 @@ namespace Microsoft.DotNet.Tests [Fact] public void ItDoesNotCreateAFirstUseSentinelFileUnderTheDotDotNetFolderWhenInternalReportInstallSuccessIsInvoked() { - var newHome = Path.Combine(_testDirectory, "new_home"); - var newHomeFolder = new DirectoryInfo(Path.Combine(newHome, ".dotnet")); + var emptyHome = Path.Combine(_testDirectory, "empty_home"); var command = new DotnetCommand() .WithWorkingDirectory(_testDirectory); - command.Environment["HOME"] = newHome; - command.Environment["USERPROFILE"] = newHome; - command.Environment["APPDATA"] = newHome; + command.Environment["HOME"] = emptyHome; + command.Environment["USERPROFILE"] = emptyHome; + command.Environment["APPDATA"] = emptyHome; command.Environment["DOTNET_CLI_TEST_FALLBACKFOLDER"] = _nugetFallbackFolder.FullName; command.Environment["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = ""; + // Disable to prevent the creation of the .dotnet folder by optimizationdata. + command.Environment["DOTNET_DISABLE_MULTICOREJIT"] = "true"; command.Environment["SkipInvalidConfigurations"] = "true"; command.ExecuteWithCapturedOutput("internal-reportinstallsuccess test").Should().Pass(); - newHomeFolder.Should().NotHaveFile($"{GetDotnetVersion()}.dotnetFirstUseSentinel"); + var emptyHomeFolder = new DirectoryInfo(Path.Combine(emptyHome, ".dotnet")); + emptyHomeFolder.Should().NotExist(); } [Fact] From 6541a16a384b76f31a33bdc0b681172f2e7395e3 Mon Sep 17 00:00:00 2001 From: Livar Cunha Date: Wed, 26 Jul 2017 15:33:48 -0700 Subject: [PATCH 3/5] Changing the FirstNoticeSentinel used by the internal-reportinstallsuccess command. --- .../InternalReportinstallsuccessCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotnet/commands/dotnet-internal-reportinstallsuccess/InternalReportinstallsuccessCommand.cs b/src/dotnet/commands/dotnet-internal-reportinstallsuccess/InternalReportinstallsuccessCommand.cs index f3ac8702f..14f940c07 100644 --- a/src/dotnet/commands/dotnet-internal-reportinstallsuccess/InternalReportinstallsuccessCommand.cs +++ b/src/dotnet/commands/dotnet-internal-reportinstallsuccess/InternalReportinstallsuccessCommand.cs @@ -43,7 +43,7 @@ namespace Microsoft.DotNet.Cli { var sessionId = Environment.GetEnvironmentVariable(TelemetrySessionIdEnvironmentVariableName); - telemetry = new Telemetry(new FirstTimeUseNoticeSentinel(new CliFallbackFolderPathCalculator()), sessionId); + telemetry = new Telemetry(new NoOpFirstTimeUseNoticeSentinel(), sessionId); } public bool Enabled => telemetry.Enabled; From c8590354a7c2d1c7ad2f1d3312edc141ef26c275 Mon Sep 17 00:00:00 2001 From: William Li Date: Wed, 26 Jul 2017 16:03:59 -0700 Subject: [PATCH 4/5] Add block thread constructor --- src/dotnet/Telemetry.cs | 26 +++++++++++++++++-- .../InternalReportinstallsuccessCommand.cs | 2 +- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/dotnet/Telemetry.cs b/src/dotnet/Telemetry.cs index 457e6f7af..cf5437a19 100644 --- a/src/dotnet/Telemetry.cs +++ b/src/dotnet/Telemetry.cs @@ -53,6 +53,28 @@ namespace Microsoft.DotNet.Cli _trackEventTask = Task.Factory.StartNew(() => InitializeTelemetry()); } + public Telemetry(IFirstTimeUseNoticeSentinel sentinel, string sessionId, bool blockThreadInitialization) + { + Enabled = !Env.GetEnvironmentVariableAsBool(TelemetryOptout) && PermissionExists(sentinel); + + if (!Enabled) + { + return; + } + + // Store the session ID in a static field so that it can be reused + CurrentSessionId = sessionId ?? Guid.NewGuid().ToString(); + + if (blockThreadInitialization) + { + InitializeTelemetry(); + } + else + { + _trackEventTask = Task.Factory.StartNew(() => InitializeTelemetry()); + } + } + private bool PermissionExists(IFirstTimeUseNoticeSentinel sentinel) { if (sentinel == null) @@ -126,9 +148,9 @@ namespace Microsoft.DotNet.Cli _client.TrackEvent(eventName, eventProperties, eventMeasurements); _client.Flush(); } - catch (Exception) + catch (Exception e) { - Debug.Fail("Exception during TrackEventTask"); + Debug.Fail(e.ToString()); } } diff --git a/src/dotnet/commands/dotnet-internal-reportinstallsuccess/InternalReportinstallsuccessCommand.cs b/src/dotnet/commands/dotnet-internal-reportinstallsuccess/InternalReportinstallsuccessCommand.cs index 14f940c07..9da815196 100644 --- a/src/dotnet/commands/dotnet-internal-reportinstallsuccess/InternalReportinstallsuccessCommand.cs +++ b/src/dotnet/commands/dotnet-internal-reportinstallsuccess/InternalReportinstallsuccessCommand.cs @@ -43,7 +43,7 @@ namespace Microsoft.DotNet.Cli { var sessionId = Environment.GetEnvironmentVariable(TelemetrySessionIdEnvironmentVariableName); - telemetry = new Telemetry(new NoOpFirstTimeUseNoticeSentinel(), sessionId); + telemetry = new Telemetry(new NoOpFirstTimeUseNoticeSentinel(), sessionId, blockThreadInitialization: true); } public bool Enabled => telemetry.Enabled; From aa3b1b67d829f26251b14edace1cbc1588684359 Mon Sep 17 00:00:00 2001 From: William Li Date: Wed, 26 Jul 2017 16:30:03 -0700 Subject: [PATCH 5/5] refactor to remove duplication --- src/dotnet/Telemetry.cs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/dotnet/Telemetry.cs b/src/dotnet/Telemetry.cs index cf5437a19..0f28c449a 100644 --- a/src/dotnet/Telemetry.cs +++ b/src/dotnet/Telemetry.cs @@ -37,23 +37,7 @@ namespace Microsoft.DotNet.Cli public Telemetry(IFirstTimeUseNoticeSentinel sentinel) : this(sentinel, null) { } - public Telemetry(IFirstTimeUseNoticeSentinel sentinel, string sessionId) - { - Enabled = !Env.GetEnvironmentVariableAsBool(TelemetryOptout) && PermissionExists(sentinel); - - if (!Enabled) - { - return; - } - - // Store the session ID in a static field so that it can be reused - CurrentSessionId = sessionId ?? Guid.NewGuid().ToString(); - - //initialize in task to offload to parallel thread - _trackEventTask = Task.Factory.StartNew(() => InitializeTelemetry()); - } - - public Telemetry(IFirstTimeUseNoticeSentinel sentinel, string sessionId, bool blockThreadInitialization) + public Telemetry(IFirstTimeUseNoticeSentinel sentinel, string sessionId, bool blockThreadInitialization = false) { Enabled = !Env.GetEnvironmentVariableAsBool(TelemetryOptout) && PermissionExists(sentinel); @@ -71,6 +55,7 @@ namespace Microsoft.DotNet.Cli } else { + //initialize in task to offload to parallel thread _trackEventTask = Task.Factory.StartNew(() => InitializeTelemetry()); } }