diff --git a/eng/Versions.MSIdentity.props b/eng/Versions.MSIdentity.props index 70e5460ab..46f49209b 100644 --- a/eng/Versions.MSIdentity.props +++ b/eng/Versions.MSIdentity.props @@ -6,7 +6,7 @@ true - 1.0.1 + 1.0.2 rtm true update } } diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs index c88efa347..3b6fbe231 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs @@ -16,7 +16,7 @@ public class ProvisioningToolOptions : IDeveloperCredentialsOptions /// /// Path to appsettings.json file - /// + /// public string? AppSettingsFilePath { get; set; } /// diff --git a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/CodeAnalysisHelper.cs b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/CodeAnalysisHelper.cs index 6c0434001..ef0247a99 100644 --- a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/CodeAnalysisHelper.cs +++ b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/CodeAnalysisHelper.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.CodeAnalysis.MSBuild; @@ -7,11 +8,13 @@ namespace Microsoft.DotNet.Scaffolding.Shared.CodeModifier public static class CodeAnalysisHelper { //helps create a CodeAnalysis.Project with project files given a project path. - public static async Task LoadCodeAnalysisProjectAsync(string projectFilePath) + public static async Task LoadCodeAnalysisProjectAsync( + string projectFilePath, + IEnumerable files) { var workspace = MSBuildWorkspace.Create(); var project = await workspace.OpenProjectAsync(projectFilePath); - var projectWithFiles = project.WithAllSourceFiles(); + var projectWithFiles = project.WithAllSourceFiles(files); project = projectWithFiles ?? project; return project; } diff --git a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/CodeChange/CodeFile.cs b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/CodeChange/CodeFile.cs index 10dc40f11..7471e5c89 100644 --- a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/CodeChange/CodeFile.cs +++ b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/CodeChange/CodeFile.cs @@ -5,11 +5,12 @@ namespace Microsoft.DotNet.Scaffolding.Shared.CodeModifier.CodeChange public class CodeFile { public Dictionary Methods { get; set; } - public CodeSnippet[] RazorChanges { get; set; } + public CodeSnippet[] Replacements { get; set; } public string AddFilePath { get; set; } public string[] Usings { get; set; } public CodeBlock[] UsingsWithOptions { get; set; } public string FileName { get; set; } + public string Extension => FileName.Substring(FileName.LastIndexOf('.') + 1); public CodeBlock[] ClassProperties { get; set; } public CodeBlock[] ClassAttributes { get; set; } public string[] GlobalVariables { get; set; } diff --git a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/ProjectExtensions.cs b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/ProjectExtensions.cs index 1ebd83e79..bd6d7a05d 100644 --- a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/ProjectExtensions.cs +++ b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/CodeModifier/ProjectExtensions.cs @@ -1,43 +1,18 @@ using System.Collections.Generic; using System.IO; -using System.Linq; namespace Microsoft.DotNet.Scaffolding.Shared.CodeModifier { internal static class ProjectExtensions { - public static CodeAnalysis.Project AddDocuments(this CodeAnalysis.Project project, IEnumerable files) + public static CodeAnalysis.Project WithAllSourceFiles(this CodeAnalysis.Project project, IEnumerable files) { foreach (string file in files) { project = project.AddDocument(file, File.ReadAllText(file)).Project; } - return project; - } - - public static CodeAnalysis.Project WithAllSourceFiles(this CodeAnalysis.Project project) - { - if (!string.IsNullOrEmpty(project.FilePath)) - { - string projectDirectory = Directory.GetParent(project.FilePath)?.FullName; - if (!string.IsNullOrEmpty(projectDirectory)) - { - var files = GetAllSourceFiles(projectDirectory); - var newProject = project.AddDocuments(files); - return newProject; - } - } - return null; - } - private static IEnumerable GetAllSourceFiles(string directoryPath) - { - var filePaths = Directory.EnumerateFiles(directoryPath, "*.cs", SearchOption.AllDirectories) - .Concat(Directory.EnumerateFiles(directoryPath, "*.cshtml", SearchOption.AllDirectories)) - .Concat(Directory.EnumerateFiles(directoryPath, "*.razor", SearchOption.AllDirectories)); - - return filePaths; + return project; } - } } diff --git a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/Project/ProjectModifierHelper.cs b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/Project/ProjectModifierHelper.cs index c5567231a..77e5d0e9e 100644 --- a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/Project/ProjectModifierHelper.cs +++ b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/Project/ProjectModifierHelper.cs @@ -426,7 +426,7 @@ internal static async Task UpdateDocument(Document document, IConsoleLogger cons var classFileTxt = await document.GetTextAsync(); // Note: Here, document.Name is the full filepath - File.WriteAllText(document.Name, classFileTxt.ToString()); + File.WriteAllText(document.Name, classFileTxt.ToString(), new UTF8Encoding(false)); consoleLogger.LogMessage($"Modified {document.Name}.\n"); } diff --git a/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.Tests/ProjectDescriptionReaderTests.cs b/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.Tests/ProjectDescriptionReaderTests.cs index bad4d898f..aefcc33ec 100644 --- a/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.Tests/ProjectDescriptionReaderTests.cs +++ b/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.Tests/ProjectDescriptionReaderTests.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.DotNet.MSIdentity.CodeReaderWriter; -using Microsoft.DotNet.MSIdentity.Project; using System; using System.Globalization; using System.IO; using System.Runtime.InteropServices; +using Microsoft.DotNet.MSIdentity.CodeReaderWriter; +using Microsoft.DotNet.MSIdentity.Project; using Xunit; using Xunit.Abstractions; @@ -21,7 +21,6 @@ public ProjectDescriptionReaderTests(ITestOutputHelper output) _testOutput = output; } - readonly ProjectDescriptionReader _projectDescriptionReader = new ProjectDescriptionReader(); readonly CodeReader _codeReader = new CodeReader(); [InlineData(@"blazorserver\blazorserver-b2c", "dotnet new blazorserver --auth IndividualB2C --aad-b2c-instance https://fabrikamb2c.b2clogin.com --client-id fdb91ff5-5ce6-41f3-bdbd-8267c817015d --domain fabrikamb2c.onmicrosoft.com", "dotnet-webapp", true)] @@ -43,20 +42,22 @@ public ProjectDescriptionReaderTests(ITestOutputHelper output) [InlineData(@"webapp\webapp-singleorg", "dotnet new webapp --auth SingleOrg --client-id 86699d80-dd21-476a-bcd1-7c1a3d471f75 --domain msidentitysamplestesting.onmicrosoft.com", "dotnet-webapp")] [InlineData(@"webapp\webapp-singleorg-callsgraph", "dotnet new webapp --auth SingleOrg --client-id 86699d80-dd21-476a-bcd1-7c1a3d471f75 --domain msidentitysamplestesting.onmicrosoft.com --calls-graph", "dotnet-webapp")] [InlineData(@"webapp\webapp-singleorg-callswebapi", "dotnet new webapp --auth SingleOrg --client-id 86699d80-dd21-476a-bcd1-7c1a3d471f75 --domain msidentitysamplestesting.onmicrosoft.com --called-api-url \"https://graph.microsoft.com/beta/me\" --called-api-scopes \"user.read\"", "dotnet-webapp")] - [Theory(Skip="Test gets stuck on macOS and Linux. Tracking https://github.com/dotnet/Scaffolding/issues/1598 for fix.")] + [Theory(Skip = "Test gets stuck on macOS and Linux. Tracking https://github.com/dotnet/Scaffolding/issues/1598 for fix.")] public void TestProjectDescriptionReader(string folderPath, string command, string expectedProjectType, bool isB2C = false) { string createdProjectFolder = CreateProjectIfNeeded(folderPath, command, "ProjectDescriptionReaderTests"); + var files = Directory.EnumerateFiles(createdProjectFolder); + var projectDescriptionReader = new ProjectDescriptionReader(files); - var projectDescription = _projectDescriptionReader.GetProjectDescription(string.Empty, createdProjectFolder); + var projectDescription = projectDescriptionReader.GetProjectDescription(string.Empty); Assert.NotNull(projectDescription); Assert.Equal(expectedProjectType, projectDescription.Identifier); var authenticationSettings = _codeReader.ReadFromFiles( - createdProjectFolder, projectDescription, - _projectDescriptionReader.projectDescriptions); + projectDescriptionReader.ProjectDescriptions, + files); bool callsGraph = folderPath.Contains(TestConstants.CallsGraph); bool callsWebApi = folderPath.Contains(TestConstants.CallsWebApi) || callsGraph; @@ -87,20 +88,23 @@ public void TestProjectDescriptionReader(string folderPath, string command, stri [InlineData(@"blazorwasm\blazorwasm-b2c", "dotnet new blazorwasm --framework net5.0 --auth IndividualB2C --client-id fdb91ff5-5ce6-41f3-bdbd-8267c817015d --domain fabrikamb2c.onmicrosoft.com", "dotnet-blazorwasm", true)] [InlineData(@"blazorwasm\blazorwasm-singleorg", "dotnet new blazorwasm --framework net5.0 --auth SingleOrg --client-id 86699d80-dd21-476a-bcd1-7c1a3d471f75", "dotnet-blazorwasm")] - [Theory(Skip="The newly created test project wants new packages that don't exist on official feeds. Will rework for next update.")] + [Theory(Skip = "The newly created test project wants new packages that don't exist on official feeds. Will rework for next update.")] public void TestProjectDescriptionReader_TemplatesWithBlazorWasm(string folderPath, string command, string expectedProjectType, bool isB2C = false) { string createdProjectFolder = CreateProjectIfNeeded(folderPath, command, "ProjectDescriptionReaderTests"); + var files = Directory.EnumerateFiles(createdProjectFolder); + + var projectDescriptionReader = new ProjectDescriptionReader(files); - var projectDescription = _projectDescriptionReader.GetProjectDescription(string.Empty, createdProjectFolder); + var projectDescription = projectDescriptionReader.GetProjectDescription(string.Empty); Assert.NotNull(projectDescription); Assert.Equal(expectedProjectType, projectDescription.Identifier); var authenticationSettings = _codeReader.ReadFromFiles( - createdProjectFolder, projectDescription, - _projectDescriptionReader.projectDescriptions); + projectDescriptionReader.ProjectDescriptions, + files); if (isB2C) { @@ -122,20 +126,22 @@ public void TestProjectDescriptionReader_TemplatesWithBlazorWasm(string folderPa //[InlineData(@"blazorwasm\blazorwasm-singleorg-callsgraph-hosted", "dotnet new blazorwasm --auth SingleOrg --api-client-id 86699d80-dd21-476a-bcd1-7c1a3d471f75 --domain msidentitysamplestesting.onmicrosoft.com --calls-graph --hosted", "dotnet-blazorwasm-hosted")] //[InlineData(@"blazorwasm\blazorwasm-singleorg-callswebapi-hosted", "dotnet new blazorwasm --auth SingleOrg --api-client-id 86699d80-dd21-476a-bcd1-7c1a3d471f75 --domain msidentitysamplestesting.onmicrosoft.com --called-api-url \"https://graph.microsoft.com/beta/me\" --called-api-scopes \"user.read\" --hosted", "dotnet-blazorwasm-hosted")] [InlineData(@"blazorwasm\blazorwasm-singleorg-hosted", "dotnet new blazorwasm --auth SingleOrg --api-client-id 86699d80-dd21-476a-bcd1-7c1a3d471f75 --domain msidentitysamplestesting.onmicrosoft.com --hosted", "dotnet-blazorwasm-hosted")] - [Theory(Skip="Test gets stuck on macOS and Linux. Tracking https://github.com/dotnet/Scaffolding/issues/1598 for fix.")] + [Theory(Skip = "Test gets stuck on macOS and Linux. Tracking https://github.com/dotnet/Scaffolding/issues/1598 for fix.")] public void TestProjectDescriptionReader_TemplatesWithBlazorWasmHosted(string folderPath, string command, string expectedProjectType) { string createdProjectFolder = CreateProjectIfNeeded(folderPath, command, "ProjectDescriptionReaderTests"); + var files = Directory.EnumerateFiles(createdProjectFolder); + var projectDescriptionReader = new ProjectDescriptionReader(files); - var projectDescription = _projectDescriptionReader.GetProjectDescription(string.Empty, createdProjectFolder); + var projectDescription = projectDescriptionReader.GetProjectDescription(string.Empty); Assert.NotNull(projectDescription); Assert.Equal(expectedProjectType, projectDescription.Identifier); var authenticationSettings = _codeReader.ReadFromFiles( - createdProjectFolder, - projectDescription, - _projectDescriptionReader.projectDescriptions); + projectDescription, + projectDescriptionReader.ProjectDescriptions, + files); // Blazorwasm now delegates twice (once to the Client [Blazor], and once to the // Server [Web API] @@ -152,15 +158,17 @@ public void TestProjectDescriptionReader_TemplatesWithBlazorWasmHosted(string fo public void TestProjectDescriptionReader_TemplatesWithNoAuth(string folderPath, string command, string expectedProjectType) { string createdProjectFolder = CreateProjectIfNeeded(folderPath, command, "NoAuthTests"); + var files = Directory.EnumerateFiles(createdProjectFolder); // TODO cache + var projectDescriptionReader = new ProjectDescriptionReader(files); - var projectDescription = _projectDescriptionReader.GetProjectDescription(string.Empty, createdProjectFolder); + var projectDescription = projectDescriptionReader.GetProjectDescription(string.Empty); Assert.NotNull(projectDescription); Assert.Equal(expectedProjectType, projectDescription.Identifier); var authenticationSettings = _codeReader.ReadFromFiles( - createdProjectFolder, projectDescription, - _projectDescriptionReader.projectDescriptions); + projectDescriptionReader.ProjectDescriptions, + files); Assert.False(authenticationSettings.ApplicationParameters.HasAuthentication); Assert.Empty(authenticationSettings.ApplicationParameters.ApiPermissions); @@ -181,9 +189,9 @@ private void AssertAuthSettings(ProjectAuthenticationSettings authenticationSett { bool IsWebApi = authenticationSettings.ApplicationParameters.IsWebApi.HasValue && authenticationSettings.ApplicationParameters.IsWebApi.Value; bool IsWebApp = authenticationSettings.ApplicationParameters.IsWebApp.HasValue && authenticationSettings.ApplicationParameters.IsWebApp.Value; - bool IsBlazorWasm = authenticationSettings.ApplicationParameters.IsBlazorWasm.HasValue && authenticationSettings.ApplicationParameters.IsBlazorWasm.Value; + bool IsBlazorWasm = authenticationSettings.ApplicationParameters.IsBlazorWasm; Assert.True(IsWebApi || IsWebApp || IsBlazorWasm); - + if (isB2C) { Assert.True(authenticationSettings.ApplicationParameters.HasAuthentication); @@ -219,7 +227,7 @@ private string CreateProjectIfNeeded(string projectFolderName, string command, s // Create the folder string parentFolder = Path.Combine(tempFolder, "Provisioning", testName); string createdProjectFolder = Path.Combine( - parentFolder, + parentFolder, projectFolderName.Replace("\\", Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture))); if (!Directory.Exists(createdProjectFolder)) diff --git a/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/AppProvisioningToolTests.cs b/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/AppProvisioningToolTests.cs new file mode 100644 index 000000000..5b06d5960 --- /dev/null +++ b/test/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity.UnitTests.Tests/AppProvisioningToolTests.cs @@ -0,0 +1,227 @@ +using Microsoft.DotNet.MSIdentity.AuthenticationParameters; +using Microsoft.DotNet.MSIdentity.MicrosoftIdentityPlatform; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.DotNet.MSIdentity.UnitTests.Tests +{ + public class AppProvisioningToolTests + { + const string existingDomain = "existing domain"; + const string existingTenantId = "existing tenant Id"; + const string existingClientId = "existing client Id"; + const string existingInstance = "existing Instance"; + const string existingCallbackPath = "existing Callback Path"; + + const string inputDomain = "input domain"; + const string inputTenantId = "input tenant Id"; + const string inputClientId = "input client Id"; + const string inputInstance = "input Instance"; + const string inputCallbackPath = "input Callback Path"; + + [Fact] + public void ModifyAppSettings_NoInput_DefaultOutput() + { + var modifier = new AppSettingsModifier(new Tool.ProvisioningToolOptions()); + var appSettings = new Newtonsoft.Json.Linq.JObject(); + var parameters = new AuthenticationParameters.ApplicationParameters(); + + var expected = JToken.FromObject(new + { + DefaultProperties.Domain, + DefaultProperties.TenantId, + DefaultProperties.ClientId, + DefaultProperties.Instance, + DefaultProperties.CallbackPath + }).ToString(); + + var modifications = modifier.GetModifiedAppSettings(appSettings, parameters)["AzureAd"].ToString(); + + Assert.Equal(expected, modifications); + } + + [Fact] + public void ModifyAppSettings_NoInput_Blazor_DefaultOutput() + { + var modifier = new AppSettingsModifier(new Tool.ProvisioningToolOptions()); + var appSettings = new Newtonsoft.Json.Linq.JObject(); + var parameters = new AuthenticationParameters.ApplicationParameters { IsBlazorWasm = true }; + + var expected = JObject.FromObject(new + { + DefaultProperties.Authority, + DefaultProperties.ClientId, + DefaultProperties.ValidateAuthority + }).ToString(); + + var modifications = modifier.GetModifiedAppSettings(appSettings, parameters)["AzureAd"].ToString(); + + Assert.Equal(expected, modifications); + } + + [Fact] + public void ModifyAppSettings_HasInput_NoExistingProperties() + { + var modifier = new AppSettingsModifier(new Tool.ProvisioningToolOptions()); + var appSettings = new Newtonsoft.Json.Linq.JObject(); + + var expected = JObject.FromObject(new + { + Domain = inputDomain, + TenantId = inputTenantId, + ClientId = inputClientId, + Instance = inputInstance, + CallbackPath = inputCallbackPath + }).ToString(); + + var parameters = new AuthenticationParameters.ApplicationParameters + { + Domain = inputDomain, + TenantId = inputTenantId, + ClientId = inputClientId, + Instance = inputInstance, + CallbackPath = inputCallbackPath + }; + + var modifications = modifier.GetModifiedAppSettings(appSettings, parameters)["AzureAd"].ToString(); + + Assert.Equal(expected, modifications); + } + + [Fact] + public void ModifyAppSettings_HasAllInputParameters_ExistingPropertiesDiffer() + { + var modifier = new AppSettingsModifier(new Tool.ProvisioningToolOptions()); + var appSettings = new Newtonsoft.Json.Linq.JObject + { + { + "AzureAd", + JToken.FromObject(new + { + Domain = existingDomain, + TenantId = existingTenantId, + ClientId = existingClientId, + Instance = existingInstance, + CallbackPath = existingCallbackPath + }) + } + }; + + var parameters = new AuthenticationParameters.ApplicationParameters + { + Domain = inputDomain, + TenantId = inputTenantId, + ClientId = inputClientId, + Instance = inputInstance, + CallbackPath = inputCallbackPath + }; + + var expected = JObject.FromObject(new + { + Domain = inputDomain, + TenantId = inputTenantId, + ClientId = inputClientId, + Instance = inputInstance, + CallbackPath = inputCallbackPath + }).ToString(); + + var modifications = modifier.GetModifiedAppSettings(appSettings, parameters)["AzureAd"].ToString(); + + Assert.Equal(expected, modifications); + } + + [Fact] + public void ModifyAppSettings_HasSomeInputParameters_ExistingPropertiesDiffer() + { + var modifier = new AppSettingsModifier(new Tool.ProvisioningToolOptions()); + + var appSettings = new Newtonsoft.Json.Linq.JObject + { + { + "AzureAd", + JToken.FromObject(new + { + Domain = existingDomain, + TenantId = existingTenantId, + ClientId = existingClientId, + Instance = existingInstance, + CallbackPath = existingCallbackPath + }) + } + }; + + var parameters = new AuthenticationParameters.ApplicationParameters + { + Domain = inputDomain + }; + + var expected = JObject.FromObject(new + { + Domain = inputDomain, + TenantId = existingTenantId, + ClientId = existingClientId, + Instance = existingInstance, + CallbackPath = existingCallbackPath + }).ToString(); + + var modifications = modifier.GetModifiedAppSettings(appSettings, parameters)["AzureAd"].ToString(); + + Assert.Equal(expected, modifications); + } + + [Fact] + public void ModifyAppSettings_HasEmptyInputParameter_ExistingPropertiesNotModified() + { + var modifier = new AppSettingsModifier(new Tool.ProvisioningToolOptions()); + + var appSettings = new Newtonsoft.Json.Linq.JObject + { + { + "AzureAd", + JToken.FromObject(new + { + Domain = existingDomain, + TenantId = existingTenantId, + ClientId = existingClientId, + Instance = existingInstance, + CallbackPath = existingCallbackPath + }) + } + }; + + var parameters = new AuthenticationParameters.ApplicationParameters + { + Domain = inputDomain, + TenantId = "", + ClientId = "" + }; + + var expected = JObject.FromObject(new + { + Domain = inputDomain, + TenantId = existingTenantId, + ClientId = existingClientId, + Instance = existingInstance, + CallbackPath = existingCallbackPath + }).ToString(); + + var modifications = modifier.GetModifiedAppSettings(appSettings, parameters)["AzureAd"].ToString(); + + Assert.Equal(expected, modifications); + } + + [Theory] + [InlineData(PropertyNames.Domain, "", "newValue", "newValue")] + [InlineData(PropertyNames.Domain, "ExistingDomain", "", null)] + [InlineData(PropertyNames.Domain, "", "", DefaultProperties.Domain)] + [InlineData(PropertyNames.Domain, null, "newValue", "newValue")] + [InlineData(PropertyNames.Domain, "ExistingDomain", null, null)] + [InlineData(PropertyNames.Domain, null, null, DefaultProperties.Domain)] + public void UpdatePropertyIfNecessary(string propertyName, string existingValue, string newValue, string expected) + { + var update = AppSettingsModifier.GetUpdatedValue(propertyName, existingValue, newValue); + + Assert.Equal(update, expected); + } + } +} diff --git a/tools/dotnet-msidentity/README.md b/tools/dotnet-msidentity/README.md index d86ce8a99..4268de955 100644 --- a/tools/dotnet-msidentity/README.md +++ b/tools/dotnet-msidentity/README.md @@ -10,7 +10,7 @@ This command will download the specified NuGet package, and then install the too in preview, you will need to specify the `--version` parameter when calling `dotnet tool install`. To install the latest version of the tool, run the following. ```Shell - dotnet tool install Microsoft.dotnet-msidentity -g --version "1.0.0-preview.2.21253.1" + dotnet tool install Microsoft.dotnet-msidentity -g --version "1.0.2" ``` To install a different version of the tool, you can find the available versions at https://www.nuget.org/packages/Microsoft.dotnet-msidentity.