diff --git a/src/Common/Commands.ResourceManager.Common/Commands.ResourceManager.Common.csproj b/src/Common/Commands.ResourceManager.Common/Commands.ResourceManager.Common.csproj
index ff1ad1950375..62da2d902735 100644
--- a/src/Common/Commands.ResourceManager.Common/Commands.ResourceManager.Common.csproj
+++ b/src/Common/Commands.ResourceManager.Common/Commands.ResourceManager.Common.csproj
@@ -166,10 +166,22 @@
+
+ True
+ True
+ Resources.resx
+
+
Designer
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
diff --git a/src/Common/Commands.ResourceManager.Common/Properties/Resources.Designer.cs b/src/Common/Commands.ResourceManager.Common/Properties/Resources.Designer.cs
new file mode 100644
index 000000000000..826b427af001
--- /dev/null
+++ b/src/Common/Commands.ResourceManager.Common/Properties/Resources.Designer.cs
@@ -0,0 +1,72 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Azure.Commands.ResourceManager.Common.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Azure.Commands.ResourceManager.Common.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Tenant '{0}' was not found. Please verify that your account has access to this tenant..
+ ///
+ internal static string TenantNotFound {
+ get {
+ return ResourceManager.GetString("TenantNotFound", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/Common/Commands.ResourceManager.Common/Properties/Resources.resx b/src/Common/Commands.ResourceManager.Common/Properties/Resources.resx
new file mode 100644
index 000000000000..7e29fac28996
--- /dev/null
+++ b/src/Common/Commands.ResourceManager.Common/Properties/Resources.resx
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Tenant '{0}' was not found. Please verify that your account has access to this tenant.
+
+
\ No newline at end of file
diff --git a/src/Common/Commands.ResourceManager.Common/RMProfileClient.cs b/src/Common/Commands.ResourceManager.Common/RMProfileClient.cs
new file mode 100644
index 000000000000..cd3908bafca1
--- /dev/null
+++ b/src/Common/Commands.ResourceManager.Common/RMProfileClient.cs
@@ -0,0 +1,164 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Hyak.Common;
+using Microsoft.Azure.Common.Authentication;
+using Microsoft.Azure.Common.Authentication.Factories;
+using Microsoft.Azure.Common.Authentication.Models;
+using Microsoft.Azure.Subscriptions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Security;
+
+namespace Microsoft.Azure.Commands.ResourceManager.Common
+{
+ public class RMProfileClient
+ {
+ private AzureRMProfile _profile;
+ public Action WarningLog;
+
+ public RMProfileClient(AzureRMProfile profile)
+ {
+ _profile = profile;
+ }
+
+ public AzureRMProfile Login(AzureAccount account, AzureEnvironment environment, string tenantId, string subscriptionId, SecureString password)
+ {
+ AzureSubscription newSubscription = null;
+ AzureTenant newTenant = new AzureTenant();
+
+ // (tenant and subscription are present) OR
+ // (tenant is present and subscription is not provided)
+ if (!string.IsNullOrEmpty(tenantId))
+ {
+ newTenant.Id = new Guid(tenantId);
+ ShowDialog promptBehavior = password == null ? ShowDialog.Always : ShowDialog.Never;
+ TryGetTenantSubscription(account, environment, tenantId, subscriptionId, password, promptBehavior, out newSubscription);
+ }
+ // (tenant is not provided and subscription is present) OR
+ // (tenant is not provided and subscription is not provided)
+ else
+ {
+ foreach(var tenant in ListAccountTenants(account, environment, password))
+ {
+ if (TryGetTenantSubscription(account, environment, tenant, subscriptionId, password, ShowDialog.Auto, out newSubscription))
+ {
+ newTenant.Id = new Guid(tenant);
+ break;
+ }
+ }
+
+ }
+
+ if (newSubscription == null)
+ {
+ throw new PSInvalidOperationException("Subscription was not found.");
+ }
+
+ _profile.DefaultContext = new AzureContext(newSubscription, account, environment, newTenant);
+
+ return _profile;
+ }
+
+ private bool TryGetTenantSubscription(
+ AzureAccount account,
+ AzureEnvironment environment,
+ string tenantId,
+ string subscriptionId,
+ SecureString password,
+ ShowDialog promptBehavior,
+ out AzureSubscription subscription)
+ {
+ var accessToken = AzureSession.AuthenticationFactory.Authenticate(
+ account,
+ environment,
+ tenantId,
+ password,
+ promptBehavior);
+ using (var subscriptionClient = AzureSession.ClientFactory.CreateCustomClient(
+ new TokenCloudCredentials(accessToken.AccessToken),
+ environment.GetEndpointAsUri(AzureEnvironment.Endpoint.ResourceManager)))
+ {
+ Subscriptions.Models.Subscription subscriptionFromServer = null;
+
+ try
+ {
+ if (subscriptionId != null)
+ {
+ subscriptionFromServer = subscriptionClient.Subscriptions.Get(subscriptionId).Subscription;
+ }
+ else
+ {
+ var subscriptions = subscriptionClient.Subscriptions.List().Subscriptions;
+ if (subscriptions != null)
+ {
+ if (subscriptions.Count > 1)
+ {
+ WriteWarningMessage(string.Format(
+ "Tenant '{0}' contains more than one subscription. First one will be selected for further use.",
+ tenantId));
+ }
+ subscriptionFromServer = subscriptions.First();
+ }
+ }
+ }
+ catch (CloudException ex)
+ {
+ WriteWarningMessage(ex.Message);
+ }
+
+ if (subscriptionFromServer != null)
+ {
+ subscription = new AzureSubscription
+ {
+ Id = new Guid(subscriptionFromServer.SubscriptionId),
+ Account = accessToken.UserId,
+ Environment = environment.Name,
+ Name = subscriptionFromServer.DisplayName,
+ Properties = new Dictionary { { AzureSubscription.Property.Tenants, accessToken.TenantId } }
+ };
+ return true;
+ }
+
+ subscription = null;
+ return false;
+ }
+ }
+
+ private string[] ListAccountTenants(AzureAccount account, AzureEnvironment environment, SecureString password)
+ {
+ ShowDialog promptBehavior = password == null ? ShowDialog.Always : ShowDialog.Never;
+
+ var commonTenantToken = AzureSession.AuthenticationFactory.Authenticate(account, environment,
+ AuthenticationFactory.CommonAdTenant, password, promptBehavior);
+
+ using (var subscriptionClient = AzureSession.ClientFactory.CreateCustomClient(
+ new TokenCloudCredentials(commonTenantToken.AccessToken),
+ environment.GetEndpointAsUri(AzureEnvironment.Endpoint.ResourceManager)))
+ {
+ return subscriptionClient.Tenants.List().TenantIds.Select(ti => ti.TenantId).ToArray();
+ }
+ }
+
+ private void WriteWarningMessage(string message)
+ {
+ if (WarningLog != null)
+ {
+ WarningLog(message);
+ }
+ }
+ }
+}
diff --git a/src/Common/Commands.ResourceManager.Profile.Test/Commands.ResourceManager.Profile.Test.csproj b/src/Common/Commands.ResourceManager.Profile.Test/Commands.ResourceManager.Profile.Test.csproj
index a2007b8519c7..b7b9311fa65c 100644
--- a/src/Common/Commands.ResourceManager.Profile.Test/Commands.ResourceManager.Profile.Test.csproj
+++ b/src/Common/Commands.ResourceManager.Profile.Test/Commands.ResourceManager.Profile.Test.csproj
@@ -178,6 +178,7 @@
+
diff --git a/src/Common/Commands.ResourceManager.Profile.Test/LoginCmdletTests.cs b/src/Common/Commands.ResourceManager.Profile.Test/LoginCmdletTests.cs
new file mode 100644
index 000000000000..a881481c0e88
--- /dev/null
+++ b/src/Common/Commands.ResourceManager.Profile.Test/LoginCmdletTests.cs
@@ -0,0 +1,128 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.WindowsAzure.Commands.Utilities.Common;
+using Microsoft.Azure.Commands.Profile;
+using Microsoft.Azure.Commands.ResourceManager.Common;
+using Microsoft.Azure.Common.Authentication;
+using Microsoft.Azure.Common.Authentication.Models;
+using Microsoft.WindowsAzure.Commands.Common.Test.Mocks;
+using Microsoft.WindowsAzure.Commands.ScenarioTest;
+using System.Linq;
+using Xunit;
+using System;
+using Microsoft.WindowsAzure.Commands.Test.Utilities.Common;
+using Hyak.Common;
+using System.Management.Automation;
+
+namespace Microsoft.Azure.Commands.Profile.Test
+{
+ public class LoginCmdletTests
+ {
+ private MemoryDataStore dataStore;
+ private MockCommandRuntime commandRuntimeMock;
+
+ public LoginCmdletTests()
+ {
+ dataStore = new MemoryDataStore();
+ AzureSession.DataStore = dataStore;
+ commandRuntimeMock = new MockCommandRuntime();
+ AzureRMCmdlet.DefaultProfile = new AzureRMProfile();
+ }
+
+ [Fact]
+ [Trait(Category.AcceptanceType, Category.LiveOnly)]
+ public void LoginWithSubscriptionAndTenant()
+ {
+ var cmdlt = new LoginAzureRMAccount();
+ // Setup
+ cmdlt.CommandRuntime = commandRuntimeMock;
+ cmdlt.SubscriptionId = "2c224e7e-3ef5-431d-a57b-e71f4662e3a6";
+ cmdlt.Tenant = "72f988bf-86f1-41af-91ab-2d7cd011db47";
+
+ // Act
+ cmdlt.InvokeBeginProcessing();
+ cmdlt.ExecuteCmdlet();
+ cmdlt.InvokeEndProcessing();
+
+ Assert.NotNull(AzureRMCmdlet.DefaultProfile.DefaultContext);
+ }
+
+ [Fact]
+ [Trait(Category.AcceptanceType, Category.LiveOnly)]
+ public void LoginWithInvalidSubscriptionAndTenantThrowsCloudException()
+ {
+ var cmdlt = new LoginAzureRMAccount();
+ // Setup
+ cmdlt.CommandRuntime = commandRuntimeMock;
+ cmdlt.SubscriptionId = "2c224e7e-3ef5-431d-a57b-e71f4662e3a5";
+ cmdlt.Tenant = "72f988bf-86f1-41af-91ab-2d7cd011db47";
+
+ // Act
+ cmdlt.InvokeBeginProcessing();
+ Assert.Throws(() => cmdlt.ExecuteCmdlet());
+ cmdlt.InvokeEndProcessing();
+ }
+
+ [Fact]
+ [Trait(Category.AcceptanceType, Category.LiveOnly)]
+ public void LoginWithSubscriptionAndNoTenant()
+ {
+ var cmdlt = new LoginAzureRMAccount();
+ // Setup
+ cmdlt.CommandRuntime = commandRuntimeMock;
+ cmdlt.SubscriptionId = "2c224e7e-3ef5-431d-a57b-e71f4662e3a6";
+
+ // Act
+ cmdlt.InvokeBeginProcessing();
+ cmdlt.ExecuteCmdlet();
+ cmdlt.InvokeEndProcessing();
+
+ Assert.NotNull(AzureRMCmdlet.DefaultProfile.DefaultContext);
+ }
+
+ [Fact]
+ [Trait(Category.AcceptanceType, Category.LiveOnly)]
+ public void LoginWithNoSubscriptionAndNoTenant()
+ {
+ var cmdlt = new LoginAzureRMAccount();
+ // Setup
+ cmdlt.CommandRuntime = commandRuntimeMock;
+
+ // Act
+ cmdlt.InvokeBeginProcessing();
+ cmdlt.ExecuteCmdlet();
+ cmdlt.InvokeEndProcessing();
+
+ Assert.NotNull(AzureRMCmdlet.DefaultProfile.DefaultContext);
+ }
+
+ [Fact]
+ [Trait(Category.AcceptanceType, Category.LiveOnly)]
+ public void LoginWithNoSubscriptionAndTenant()
+ {
+ var cmdlt = new LoginAzureRMAccount();
+ // Setup
+ cmdlt.CommandRuntime = commandRuntimeMock;
+ cmdlt.Tenant = "72f988bf-86f1-41af-91ab-2d7cd011db47";
+
+ // Act
+ cmdlt.InvokeBeginProcessing();
+ cmdlt.ExecuteCmdlet();
+ cmdlt.InvokeEndProcessing();
+
+ Assert.NotNull(AzureRMCmdlet.DefaultProfile.DefaultContext);
+ }
+ }
+}
diff --git a/src/Common/Commands.ResourceManager.Profile/Commands.ResourceManager.Profile.csproj b/src/Common/Commands.ResourceManager.Profile/Commands.ResourceManager.Profile.csproj
index c3b20d9827a2..5ff4ba4ddab5 100644
--- a/src/Common/Commands.ResourceManager.Profile/Commands.ResourceManager.Profile.csproj
+++ b/src/Common/Commands.ResourceManager.Profile/Commands.ResourceManager.Profile.csproj
@@ -132,6 +132,7 @@
+
diff --git a/src/Common/Commands.ResourceManager.Profile/LoginAzureRMAccount.cs b/src/Common/Commands.ResourceManager.Profile/LoginAzureRMAccount.cs
new file mode 100644
index 000000000000..3d8f5d85bf7f
--- /dev/null
+++ b/src/Common/Commands.ResourceManager.Profile/LoginAzureRMAccount.cs
@@ -0,0 +1,117 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.ResourceManager.Common;
+using System.Management.Automation;
+using Microsoft.Azure.Common.Authentication.Models;
+using System.Security;
+using Microsoft.WindowsAzure.Commands.Utilities.Common;
+
+namespace Microsoft.Azure.Commands.Profile
+{
+ ///
+ /// Cmdlet to log into an environment and download the subscriptions
+ ///
+ [Cmdlet("Login", "AzureRMAccount", DefaultParameterSetName = "User")]
+ [OutputType(typeof(AzureRMProfile))]
+ public class LoginAzureRMAccount : AzurePSCmdlet
+ {
+ [Parameter(Mandatory = false, HelpMessage = "Environment containing the account to log into")]
+ [ValidateNotNullOrEmpty]
+ public AzureEnvironment Environment { get; set; }
+
+ [Parameter(ParameterSetName = "User", Mandatory = false, HelpMessage = "Optional credential")]
+ [Parameter(ParameterSetName = "ServicePrincipal", Mandatory = true, HelpMessage = "Credential")]
+ public PSCredential Credential { get; set; }
+
+ [Parameter(ParameterSetName = "ServicePrincipal", Mandatory = true)]
+ public SwitchParameter ServicePrincipal { get; set; }
+
+ [Parameter(ParameterSetName = "User", Mandatory = false, HelpMessage = "Optional tenant name or ID")]
+ [Parameter(ParameterSetName = "ServicePrincipal", Mandatory = true, HelpMessage = "Tenant name or ID")]
+ [Parameter(ParameterSetName = "AccessToken", Mandatory = false, HelpMessage = "Tenant name or ID")]
+ [ValidateNotNullOrEmpty]
+ public string Tenant { get; set; }
+
+ [Parameter(ParameterSetName = "AccessToken", Mandatory = true, HelpMessage = "AccessToken")]
+ [ValidateNotNullOrEmpty]
+ public string AccessToken { get; set; }
+
+ [Parameter(Mandatory = false, HelpMessage = "Subscription")]
+ [ValidateNotNullOrEmpty]
+ public string SubscriptionId { get; set; }
+
+ protected override AzureContext DefaultContext
+ {
+ get
+ {
+ return null;
+ }
+ }
+
+ public LoginAzureRMAccount()
+ : base()
+ {
+ }
+
+ protected override void BeginProcessing()
+ {
+ base.BeginProcessing();
+ if (Environment == null)
+ {
+ Environment = AzureEnvironment.PublicEnvironments[EnvironmentName.AzureCloud];
+ }
+ }
+
+ protected override void ProcessRecord()
+ {
+ AzureAccount azureAccount = new AzureAccount();
+
+ if (!string.IsNullOrEmpty(AccessToken))
+ {
+ azureAccount.Type = AzureAccount.AccountType.AccessToken;
+ }
+ else if (ServicePrincipal.IsPresent)
+ {
+ azureAccount.Type = AzureAccount.AccountType.ServicePrincipal;
+ }
+ else
+ {
+ azureAccount.Type = AzureAccount.AccountType.User;
+
+ }
+
+ SecureString password = null;
+ if (Credential != null)
+ {
+ azureAccount.Id = Credential.UserName;
+ password = Credential.Password;
+ }
+
+ if (!string.IsNullOrEmpty(Tenant))
+ {
+ azureAccount.SetProperty(AzureAccount.Property.Tenants, new[] { Tenant });
+ }
+
+ if( AzureRMCmdlet.DefaultProfile == null)
+ {
+ AzureRMCmdlet.DefaultProfile = new AzureRMProfile();
+ }
+
+ var profileClient = new RMProfileClient(AzureRMCmdlet.DefaultProfile);
+
+ WriteObject(profileClient.Login(azureAccount, Environment, Tenant, SubscriptionId, password));
+ }
+ }
+}
diff --git a/src/ResourceManager.sln b/src/ResourceManager.sln
index b793d8fdf990..eae93fd6ef08 100644
--- a/src/ResourceManager.sln
+++ b/src/ResourceManager.sln
@@ -121,8 +121,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commands.Websites.Test", "R
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commands.Common", "Common\Commands.Common\Commands.Common.csproj", "{5EE72C53-1720-4309-B54B-5FB79703195F}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commands.Profile", "Common\Commands.Profile\Commands.Profile.csproj", "{C60342B1-47D3-4A0E-8081-9B97CE60B7AF}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -349,10 +347,6 @@ Global
{5EE72C53-1720-4309-B54B-5FB79703195F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5EE72C53-1720-4309-B54B-5FB79703195F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5EE72C53-1720-4309-B54B-5FB79703195F}.Release|Any CPU.Build.0 = Release|Any CPU
- {C60342B1-47D3-4A0E-8081-9B97CE60B7AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C60342B1-47D3-4A0E-8081-9B97CE60B7AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C60342B1-47D3-4A0E-8081-9B97CE60B7AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C60342B1-47D3-4A0E-8081-9B97CE60B7AF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -382,6 +376,5 @@ Global
{152D78F0-A642-4D0E-B3A8-2FC64FFA9714} = {95C16AED-FD57-42A0-86C3-2CF4300A4817}
{F220C306-29A3-4511-8518-A58A55C60D07} = {95C16AED-FD57-42A0-86C3-2CF4300A4817}
{13E031E4-8A43-4B87-9D72-D70180C31C11} = {95C16AED-FD57-42A0-86C3-2CF4300A4817}
- {C60342B1-47D3-4A0E-8081-9B97CE60B7AF} = {95C16AED-FD57-42A0-86C3-2CF4300A4817}
EndGlobalSection
EndGlobal