diff --git a/src/ResourceManager/Profile/ChangeLog.md b/src/ResourceManager/Profile/ChangeLog.md index 4f0a31e78337..421892d96218 100644 --- a/src/ResourceManager/Profile/ChangeLog.md +++ b/src/ResourceManager/Profile/ChangeLog.md @@ -18,6 +18,7 @@ - Additional information about change #1 --> ## Current Release +* Create a context for each subscription when running `Connect-AzureRmAccount` with no previous context ## Version 5.2.0 * Added the following three values to the telemetry: diff --git a/src/ResourceManager/Profile/Commands.Profile.Test/LoginCmdletTests.cs b/src/ResourceManager/Profile/Commands.Profile.Test/LoginCmdletTests.cs index 8a53d2b3f3f5..e83efc143d2e 100644 --- a/src/ResourceManager/Profile/Commands.Profile.Test/LoginCmdletTests.cs +++ b/src/ResourceManager/Profile/Commands.Profile.Test/LoginCmdletTests.cs @@ -31,6 +31,7 @@ using Microsoft.Azure.Commands.Common.Authentication.Abstractions; using Microsoft.Azure.Commands.ResourceManager.Common; using Microsoft.Azure.Commands.ScenarioTest; +using System.Linq; namespace Microsoft.Azure.Commands.Profile.Test { @@ -327,7 +328,7 @@ public void LoginWithCredentialParameterAndMSA() "For more information, please refer to http://go.microsoft.com/fwlink/?linkid=331007&clcid=0x409 " + "for more information about the difference between an organizational account and a Microsoft account.", ex.Message); - } + } } [Fact] @@ -353,6 +354,43 @@ public void LoginWithAccessToken() Assert.NotNull(AzureRmProfileProvider.Instance.Profile.DefaultContext); } + [Fact] + [Trait(Category.RunType, Category.LiveOnly)] + public void LoginPopulatesContextList() + { + // Before running this test, make sure to clear the contexts on your machine by removing the following two files: + // - %APPDATA%/Windows Azure Powershell/AzureRmContext.json + // - %APPDATA%/Windows Azure Powershell/AzureRmContextSettings.json + // This will clear all existing contexts on your machine so that this test can re-populate the list with a context for each subscription + + var cmdlt = new ConnectAzureRmAccountCommand(); + // Setup + cmdlt.CommandRuntime = commandRuntimeMock; + + // Act + cmdlt.InvokeBeginProcessing(); + cmdlt.ExecuteCmdlet(); + cmdlt.InvokeEndProcessing(); + + var profile = AzureRmProfileProvider.Instance.Profile as AzureRmProfile; + Assert.NotNull(profile); + Assert.NotNull(profile.Contexts); + Assert.NotNull(profile.Subscriptions); + Assert.True(profile.Contexts.Count > 1); + Assert.True(profile.Subscriptions.Count() > 1); + Assert.Equal(profile.Subscriptions.Count(), profile.Contexts.Count); + + foreach (var sub in profile.Subscriptions) + { + var contextName = string.Format("{0} - {1}", sub.Name, sub.Id); + Assert.True(profile.Contexts.ContainsKey(contextName)); + var context = profile.Contexts[contextName]; + Assert.NotNull(context); + Assert.Equal(sub.Id, context.Subscription.Id); + Assert.Equal(sub.GetTenant(), context.Tenant.Id); + } + } + [Fact] [Trait(Category.AcceptanceType, Category.CheckIn)] public void ThrowOnUnknownEnvironment() diff --git a/src/ResourceManager/Profile/Commands.Profile/Account/ConnectAzureRmAccount.cs b/src/ResourceManager/Profile/Commands.Profile/Account/ConnectAzureRmAccount.cs index 06a261585b08..607515156265 100644 --- a/src/ResourceManager/Profile/Commands.Profile/Account/ConnectAzureRmAccount.cs +++ b/src/ResourceManager/Profile/Commands.Profile/Account/ConnectAzureRmAccount.cs @@ -50,7 +50,7 @@ public class ConnectAzureRmAccountCommand : AzureContextModificationCmdlet, IMod [Alias("EnvironmentName")] [ValidateNotNullOrEmpty] public string Environment { get; set; } - + #if !NETSTANDARD [Parameter(ParameterSetName = UserParameterSet, Mandatory = false, HelpMessage = "Optional credential", Position = 0)] @@ -59,35 +59,35 @@ public class ConnectAzureRmAccountCommand : AzureContextModificationCmdlet, IMod Mandatory = true, HelpMessage = "Credential")] public PSCredential Credential { get; set; } - [Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet, + [Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet, Mandatory = true, HelpMessage = "Certificate Hash (Thumbprint)")] public string CertificateThumbprint { get; set; } - - [Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet, + + [Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet, Mandatory = true, HelpMessage = "SPN")] public string ApplicationId { get; set; } - [Parameter(ParameterSetName = ServicePrincipalParameterSet, + [Parameter(ParameterSetName = ServicePrincipalParameterSet, Mandatory = true)] - [Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet, + [Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet, Mandatory = true)] public SwitchParameter ServicePrincipal { get; set; } - - [Parameter(ParameterSetName = UserParameterSet, + + [Parameter(ParameterSetName = UserParameterSet, Mandatory = false, HelpMessage = "Optional tenant name or ID")] - [Parameter(ParameterSetName = ServicePrincipalParameterSet, + [Parameter(ParameterSetName = ServicePrincipalParameterSet, Mandatory = true, HelpMessage = "Tenant name or ID")] - [Parameter(ParameterSetName = AccessTokenParameterSet, + [Parameter(ParameterSetName = AccessTokenParameterSet, Mandatory = false, HelpMessage = "Tenant name or ID")] - [Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet, + [Parameter(ParameterSetName = ServicePrincipalCertificateParameterSet, Mandatory = true, HelpMessage = "Tenant name or ID")] [Parameter(ParameterSetName = ManagedServiceParameterSet, Mandatory = false, HelpMessage = "Optional tenant name or ID")] [Alias("Domain")] [ValidateNotNullOrEmpty] public string TenantId { get; set; } - - [Parameter(ParameterSetName = AccessTokenParameterSet, + + [Parameter(ParameterSetName = AccessTokenParameterSet, Mandatory = true, HelpMessage = "AccessToken for Azure Resource Manager")] [ValidateNotNullOrEmpty] public string AccessToken { get; set; } @@ -101,8 +101,8 @@ public class ConnectAzureRmAccountCommand : AzureContextModificationCmdlet, IMod Mandatory = false, HelpMessage = "AccessToken for KeyVault Service")] [ValidateNotNullOrEmpty] public string KeyVaultAccessToken { get; set; } - - [Parameter(ParameterSetName = AccessTokenParameterSet, + + [Parameter(ParameterSetName = AccessTokenParameterSet, Mandatory = true, HelpMessage = "Account Id for access token")] [Parameter(ParameterSetName = ManagedServiceParameterSet, Mandatory = false, HelpMessage = "Account Id for managed service. Can be a managed service resource Id, or the associated client id. To use the SyatemAssigned identity, leave this field blank.")] @@ -148,6 +148,9 @@ public class ConnectAzureRmAccountCommand : AzureContextModificationCmdlet, IMod Mandatory = false, HelpMessage = "Skip validation for access token")] public SwitchParameter SkipValidation { get; set; } + [Parameter(Mandatory = false, HelpMessage = "Skips context population if no contexts are found.")] + public SwitchParameter SkipContextPopulation { get; set; } + [Parameter(Mandatory = false, HelpMessage = "Overwrite the existing context with the same name, if any.")] public SwitchParameter Force { get; set; } @@ -214,8 +217,8 @@ public override void ExecuteCmdlet() builder.Port = ManagedServicePort; builder.Path = "/oauth2/token"; - string msiSecret = this.IsBound(nameof(ManagedServiceSecret)) - ? ManagedServiceSecret.ConvertToString() + string msiSecret = this.IsBound(nameof(ManagedServiceSecret)) + ? ManagedServiceSecret.ConvertToString() : System.Environment.GetEnvironmentVariable(MSISecretVariable); string suppliedUri = this.IsBound(nameof(ManagedServiceHostName)) @@ -226,7 +229,7 @@ public override void ExecuteCmdlet() { azureAccount.SetProperty(AzureAccount.Property.MSILoginSecret, msiSecret); } - + if (!string.IsNullOrWhiteSpace(suppliedUri)) { azureAccount.SetProperty(AzureAccount.Property.MSILoginUri, suppliedUri); @@ -255,7 +258,7 @@ public override void ExecuteCmdlet() { azureAccount.Id = ApplicationId; } - + if (!string.IsNullOrWhiteSpace(CertificateThumbprint)) { azureAccount.SetThumbprint(CertificateThumbprint); @@ -284,7 +287,8 @@ public override void ExecuteCmdlet() password, SkipValidation, (s) => WriteWarning(s), - name)); + name, + !this.SkipContextPopulation.IsPresent)); }); } } @@ -330,7 +334,7 @@ public void OnImport() #if DEBUG } #endif - + bool autoSaveEnabled = AzureSession.Instance.ARMContextSaveMode == ContextSaveMode.CurrentUser; var autosaveVariable = System.Environment.GetEnvironmentVariable(AzureProfileConstants.AzureAutosaveVariable); bool localAutosave; diff --git a/src/ResourceManager/Profile/Commands.Profile/Models/RMProfileClient.cs b/src/ResourceManager/Profile/Commands.Profile/Models/RMProfileClient.cs index 9b6c90c6fc6e..a9e235ce2cbb 100644 --- a/src/ResourceManager/Profile/Commands.Profile/Models/RMProfileClient.cs +++ b/src/ResourceManager/Profile/Commands.Profile/Models/RMProfileClient.cs @@ -109,7 +109,8 @@ public AzureRmProfile Login( SecureString password, bool skipValidation, Action promptAction, - string name = null) + string name = null, + bool shouldPopulateContextList = true) { IAzureSubscription newSubscription = null; IAzureTenant newTenant = null; @@ -226,6 +227,7 @@ public AzureRmProfile Login( } } + shouldPopulateContextList &= _profile.DefaultContext?.Account == null; if (newSubscription == null) { if (subscriptionId != null) @@ -260,6 +262,35 @@ public AzureRmProfile Login( } _profile.DefaultContext.TokenCache = _cache; + if (shouldPopulateContextList) + { + var defaultContext = _profile.DefaultContext; + var subscriptions = ListSubscriptions(tenantId).Take(25); + foreach (var subscription in subscriptions) + { + IAzureTenant tempTenant = new AzureTenant() + { + Id = subscription.GetProperty(AzureSubscription.Property.Tenants) + }; + + var tempContext = new AzureContext(subscription, account, environment, tempTenant); + tempContext.TokenCache = _cache; + string tempName = null; + if (!_profile.TryGetContextName(tempContext, out tempName)) + { + WriteWarningMessage(string.Format(Resources.CannotGetContextName, subscription.Id)); + continue; + } + + if (!_profile.TrySetContext(tempName, tempContext)) + { + WriteWarningMessage(string.Format(Resources.CannotCreateContext, subscription.Id)); + } + } + + _profile.TrySetDefaultContext(defaultContext); + _profile.TryRemoveContext("Default"); + } return _profile.ToProfile(); } diff --git a/src/ResourceManager/Profile/Commands.Profile/Properties/Resources.Designer.cs b/src/ResourceManager/Profile/Commands.Profile/Properties/Resources.Designer.cs index 789c59e0c39e..6c3e74b2eb2f 100644 --- a/src/ResourceManager/Profile/Commands.Profile/Properties/Resources.Designer.cs +++ b/src/ResourceManager/Profile/Commands.Profile/Properties/Resources.Designer.cs @@ -159,6 +159,24 @@ internal static string BothSubscriptionIdAndNameProvided { } } + /// + /// Looks up a localized string similar to Cannot create a context for subscription with id '{0}'.. + /// + internal static string CannotCreateContext { + get { + return ResourceManager.GetString("CannotCreateContext", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to get context name for subscription with id '{0}'.. + /// + internal static string CannotGetContextName { + get { + return ResourceManager.GetString("CannotGetContextName", resourceCulture); + } + } + /// /// Looks up a localized string similar to Unable to set default context '{0}'.. /// diff --git a/src/ResourceManager/Profile/Commands.Profile/Properties/Resources.resx b/src/ResourceManager/Profile/Commands.Profile/Properties/Resources.resx index 2c0ccffa2af3..c02bbd8fb1ab 100644 --- a/src/ResourceManager/Profile/Commands.Profile/Properties/Resources.resx +++ b/src/ResourceManager/Profile/Commands.Profile/Properties/Resources.resx @@ -406,4 +406,12 @@ Run Connect-AzureRmAccount to login. + + Cannot create a context for subscription with id '{0}'. + {0} = subscription id + + + Unable to get context name for subscription with id '{0}'. + {0} = subscription id + \ No newline at end of file diff --git a/src/ResourceManager/Profile/Commands.Profile/help/Connect-AzureRmAccount.md b/src/ResourceManager/Profile/Commands.Profile/help/Connect-AzureRmAccount.md index c7d71e3e117a..ca8547a57128 100644 --- a/src/ResourceManager/Profile/Commands.Profile/help/Connect-AzureRmAccount.md +++ b/src/ResourceManager/Profile/Commands.Profile/help/Connect-AzureRmAccount.md @@ -1,4 +1,4 @@ ---- +--- external help file: Microsoft.Azure.Commands.Profile.dll-Help.xml Module Name: AzureRM.Profile online version: https://docs.microsoft.com/en-us/powershell/module/azurerm.profile/add-azurermaccount @@ -15,14 +15,15 @@ Connect to Azure with an authenticated account for use with Azure Resource Manag ### UserWithSubscriptionId (Default) ``` Connect-AzureRmAccount [-Environment ] [[-Credential] ] [-TenantId ] - [-Subscription ] [-ContextName ] [-Force] [-Scope ] - [-DefaultProfile ] [-WhatIf] [-Confirm] [] + [-Subscription ] [-ContextName ] [-SkipContextPopulation] [-Force] + [-Scope ] [-DefaultProfile ] [-WhatIf] [-Confirm] + [] ``` ### ServicePrincipalWithSubscriptionId ``` Connect-AzureRmAccount [-Environment ] [-Credential] [-ServicePrincipal] - -TenantId [-Subscription ] [-ContextName ] [-Force] + -TenantId [-Subscription ] [-ContextName ] [-SkipContextPopulation] [-Force] [-Scope ] [-DefaultProfile ] [-WhatIf] [-Confirm] [] ``` @@ -30,25 +31,27 @@ Connect-AzureRmAccount [-Environment ] [-Credential] [-Se ### ServicePrincipalCertificateWithSubscriptionId ``` Connect-AzureRmAccount [-Environment ] -CertificateThumbprint -ApplicationId - [-ServicePrincipal] -TenantId [-Subscription ] [-ContextName ] [-Force] - [-Scope ] [-DefaultProfile ] [-WhatIf] [-Confirm] - [] + [-ServicePrincipal] -TenantId [-Subscription ] [-ContextName ] + [-SkipContextPopulation] [-Force] [-Scope ] + [-DefaultProfile ] [-WhatIf] [-Confirm] [] ``` ### AccessTokenWithSubscriptionId ``` Connect-AzureRmAccount [-Environment ] [-TenantId ] -AccessToken [-GraphAccessToken ] [-KeyVaultAccessToken ] -AccountId [-Subscription ] - [-ContextName ] [-SkipValidation] [-Force] [-Scope ] - [-DefaultProfile ] [-WhatIf] [-Confirm] [] + [-ContextName ] [-SkipValidation] [-SkipContextPopulation] [-Force] + [-Scope ] [-DefaultProfile ] [-WhatIf] [-Confirm] + [] ``` ### ManagedServiceLogin ``` Connect-AzureRmAccount [-Environment ] [-TenantId ] [-AccountId ] [-Identity] - [-ManagedServicePort ] [-ManagedServiceHostName ] [-Subscription ] - [-ContextName ] [-Force] [-Scope ] - [-DefaultProfile ] [-WhatIf] [-Confirm] [] + [-ManagedServicePort ] [-ManagedServiceHostName ] [-ManagedServiceSecret ] + [-Subscription ] [-ContextName ] [-SkipContextPopulation] [-Force] + [-Scope ] [-DefaultProfile ] [-WhatIf] [-Confirm] + [] ``` ## DESCRIPTION @@ -58,6 +61,8 @@ You can use this authenticated account only with Azure Resource Manager cmdlets. To add an authenticated account for use with Service Management cmdlets, use the Add-AzureAccount or the Import-AzurePublishSettingsFile cmdlet. +If no context is found for the current user, this command will populate the user's context list with a context for each of their (first 25) subscriptions. The list of contexts created for the user can be found by running "Get-AzureRmContext -ListAvailable". To skip this context population, you can run this command with the "-SkipContextPopulation" switch parameter. + After executing this cmdlet, you can disconnect from an Azure account using Disconnect-AzureRmAccount. ## EXAMPLES @@ -142,7 +147,7 @@ Specifies an access token. ```yaml Type: String Parameter Sets: AccessTokenWithSubscriptionId -Aliases: +Aliases: Required: True Position: Named @@ -157,7 +162,7 @@ Account Id for access token ```yaml Type: String Parameter Sets: AccessTokenWithSubscriptionId -Aliases: +Aliases: Required: True Position: Named @@ -169,7 +174,7 @@ Accept wildcard characters: False ```yaml Type: String Parameter Sets: ManagedServiceLogin -Aliases: +Aliases: Required: False Position: Named @@ -184,7 +189,7 @@ SPN ```yaml Type: String Parameter Sets: ServicePrincipalCertificateWithSubscriptionId -Aliases: +Aliases: Required: True Position: Named @@ -199,7 +204,7 @@ Certificate Hash (Thumbprint) ```yaml Type: String Parameter Sets: ServicePrincipalCertificateWithSubscriptionId -Aliases: +Aliases: Required: True Position: Named @@ -214,7 +219,7 @@ Name of the default context from this login. You will be able to select this co ```yaml Type: String Parameter Sets: (All) -Aliases: +Aliases: Required: False Position: Named @@ -232,7 +237,7 @@ The PSCredential object provides the user ID and password for organizational ID ```yaml Type: PSCredential Parameter Sets: UserWithSubscriptionId -Aliases: +Aliases: Required: False Position: 0 @@ -244,7 +249,7 @@ Accept wildcard characters: False ```yaml Type: PSCredential Parameter Sets: ServicePrincipalWithSubscriptionId -Aliases: +Aliases: Required: True Position: 0 @@ -289,7 +294,7 @@ Overwrite the existing context with the same name, if any. ```yaml Type: SwitchParameter Parameter Sets: (All) -Aliases: +Aliases: Required: False Position: Named @@ -304,7 +309,7 @@ AccessToken for Graph Service ```yaml Type: String Parameter Sets: AccessTokenWithSubscriptionId -Aliases: +Aliases: Required: False Position: Named @@ -334,7 +339,7 @@ AccessToken for KeyVault Service ```yaml Type: String Parameter Sets: AccessTokenWithSubscriptionId -Aliases: +Aliases: Required: False Position: Named @@ -349,7 +354,7 @@ Host name for managed service login ```yaml Type: String Parameter Sets: ManagedServiceLogin -Aliases: +Aliases: Required: False Position: Named @@ -364,7 +369,7 @@ Port number for managed service login ```yaml Type: Int32 Parameter Sets: ManagedServiceLogin -Aliases: +Aliases: Required: False Position: Named @@ -373,13 +378,28 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -ManagedServiceSecret +Secret, used for some kinds of managed service login. + +```yaml +Type: SecureString +Parameter Sets: ManagedServiceLogin +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Scope Determines the scope of context changes, for example, whether changes apply only to the current process, or to all sessions started by this user. ```yaml Type: ContextModificationScope Parameter Sets: (All) -Aliases: +Aliases: Accepted values: Process, CurrentUser Required: False @@ -395,7 +415,7 @@ Indicates that this account authenticates by providing service principal credent ```yaml Type: SwitchParameter Parameter Sets: ServicePrincipalWithSubscriptionId, ServicePrincipalCertificateWithSubscriptionId -Aliases: +Aliases: Required: True Position: Named @@ -404,13 +424,28 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -SkipContextPopulation +Skips context population if no contexts are found. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -SkipValidation Skip validation for access token ```yaml Type: SwitchParameter Parameter Sets: AccessTokenWithSubscriptionId -Aliases: +Aliases: Required: False Position: Named @@ -510,4 +545,3 @@ Credentials, subscription, account, and tenant information for the logged in use ## NOTES ## RELATED LINKS -