diff --git a/setup/azurecmdfiles.wxi b/setup/azurecmdfiles.wxi index aaacb565c8e3..a9ecb3dc26ed 100644 --- a/setup/azurecmdfiles.wxi +++ b/setup/azurecmdfiles.wxi @@ -423,6 +423,9 @@ + + + @@ -489,6 +492,9 @@ + + + @@ -543,9 +549,15 @@ + + + + + + @@ -1715,6 +1727,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2768,6 +2860,7 @@ + @@ -2790,6 +2883,7 @@ + @@ -2808,7 +2902,9 @@ + + @@ -3189,6 +3285,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AzurePowershell.sln b/src/AzurePowershell.sln index 3d1828d1a877..1773be208242 100644 --- a/src/AzurePowershell.sln +++ b/src/AzurePowershell.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 +VisualStudioVersion = 12.0.30723.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8531411A-0137-4E27-9C5E-49E07C245048}" ProjectSection(SolutionItems) = preProject @@ -165,6 +165,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commands.Insights.Test", "R EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commands.Websites", "ResourceManager\Websites\Commands.Websites\Commands.Websites.csproj", "{80A92297-7C92-456B-8EE7-9FB6CE30149D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commands.RemoteApp", "ServiceManagement\RemoteApp\Commands.RemoteApp\Commands.RemoteApp.csproj", "{492D2AF2-950B-4F2E-8079-8794305313FD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -403,6 +405,10 @@ Global {80A92297-7C92-456B-8EE7-9FB6CE30149D}.Debug|Any CPU.Build.0 = Debug|Any CPU {80A92297-7C92-456B-8EE7-9FB6CE30149D}.Release|Any CPU.ActiveCfg = Release|Any CPU {80A92297-7C92-456B-8EE7-9FB6CE30149D}.Release|Any CPU.Build.0 = Release|Any CPU + {492D2AF2-950B-4F2E-8079-8794305313FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {492D2AF2-950B-4F2E-8079-8794305313FD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {492D2AF2-950B-4F2E-8079-8794305313FD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {492D2AF2-950B-4F2E-8079-8794305313FD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ResourceManager/Resources/Commands.Resources/Commands.Resources.csproj b/src/ResourceManager/Resources/Commands.Resources/Commands.Resources.csproj index 133888671c68..8c3ae699da97 100644 --- a/src/ResourceManager/Resources/Commands.Resources/Commands.Resources.csproj +++ b/src/ResourceManager/Resources/Commands.Resources/Commands.Resources.csproj @@ -232,6 +232,10 @@ {c60342b1-47d3-4a0e-8081-9b97ce60b7af} Commands.Profile + + {492d2af2-950b-4f2e-8079-8794305313fd} + Commands.RemoteApp + {11524d98-6c40-4091-a8e1-86463fee607c} Commands.StorSimple diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Billing/GetAzureRemoteAppPlan.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Billing/GetAzureRemoteAppPlan.cs new file mode 100644 index 000000000000..8fab7f0f2607 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Billing/GetAzureRemoteAppPlan.cs @@ -0,0 +1,39 @@ +// ---------------------------------------------------------------------------------- +// +// 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.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Collections.Generic; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppPlan"), OutputType(typeof(BillingPlan))] + public class GetAzureRemoteAppPlan : RdsCmdlet + { + public override void ExecuteCmdlet() + { + ListBillingPlansResult billingPlans = CallClient(() => Client.Account.ListBillingPlans(), Client.Account); + + if (billingPlans.PlanList.Count > 0) + { + WriteObject(billingPlans.PlanList, true); + } + else + { + WriteVerboseWithTimestamp(Commands_RemoteApp.NoPlansFoundMessage); + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/GetAzureRemoteAppCollection.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/GetAzureRemoteAppCollection.cs new file mode 100644 index 000000000000..8dff0deee61c --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/GetAzureRemoteAppCollection.cs @@ -0,0 +1,112 @@ +// ---------------------------------------------------------------------------------- +// +// 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.RemoteApp; +using Microsoft.Azure.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppCollection"), OutputType(typeof(LocalModels.Collection))] + public class GetAzureRemoteAppCollection : RdsCmdlet + { + [Parameter(Mandatory = false, + Position = 1, + HelpMessage = "RemoteApp collection name. Wildcards are permitted.")] + [ValidatePattern(NameValidatorStringWithWildCards)] + public string CollectionName { get; set; } + + private bool showAllCollections = false; + + private bool found = false; + + private bool GetAllCollections() + { + CollectionListResult response = null; + IEnumerable spList = null; + LocalModels.Collection collection = null; + + response = CallClient(() => Client.Collections.List(), Client.Collections); + + if (response != null) + { + if (UseWildcard) + { + spList = response.Collections.Where(col => Wildcard.IsMatch(col.Name)); + } + else + { + spList = response.Collections; + } + + if (spList != null && spList.Count() > 0) + { + foreach( Collection c in spList) + { + collection = new LocalModels.Collection(c); + WriteObject(collection); + } + found = true; + } + } + + return found; + } + + private bool GetCollection(string collectionName) + { + CollectionResult response = null; + LocalModels.Collection collection = null; + + response = CallClient(() => Client.Collections.Get(collectionName), Client.Collections); + + if (response != null) + { + collection = new LocalModels.Collection(response.Collection); + WriteObject(collection); + found = true; + } + + return found; + } + + public override void ExecuteCmdlet() + { + showAllCollections = String.IsNullOrWhiteSpace(CollectionName); + + if (showAllCollections == false) + { + CreateWildcardPattern(CollectionName); + } + + if (ExactMatch) + { + found = GetCollection(CollectionName); + } + else + { + found = GetAllCollections(); + } + + if (!found) + { + WriteVerboseWithTimestamp(String.Format(Commands_RemoteApp.CollectionNotFoundByNameFormat, CollectionName)); + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/GetAzureRemoteAppCollectionUsageDetails.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/GetAzureRemoteAppCollectionUsageDetails.cs new file mode 100644 index 000000000000..dd23493b99bb --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/GetAzureRemoteAppCollectionUsageDetails.cs @@ -0,0 +1,146 @@ +// ---------------------------------------------------------------------------------- +// +// 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.RemoteApp; +using Microsoft.Azure.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Management.Automation; +using System.Net; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppCollectionUsageDetails"), OutputType(typeof(string))] + public class GetAzureRemoteAppCollectionUsageDetails : RdsCmdlet + { + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipelineByPropertyName = true, + HelpMessage = "RemoteApp collection name")] + public string CollectionName { get; set; } + + [Parameter(Mandatory = false, + Position = 1, + HelpMessage = "Number of the month (MM) to report usage")] + [ValidatePattern(TwoDigitMonthPattern)] + public string UsageMonth { get; set; } + + [Parameter(Mandatory = false, + Position = 2, + HelpMessage = "Year (YYYY) to report usage")] + [ValidatePattern(FullYearPattern)] + public string UsageYear { get; set; } + + public override void ExecuteCmdlet() + { + DateTime today = DateTime.Now; + CollectionUsageDetailsResult detailsUsage = null; + string locale = String.Empty; + RemoteAppOperationStatusResult operationResult = null; + int maxRetryCount = 600; + + if (String.IsNullOrWhiteSpace(UsageMonth)) + { + UsageMonth = today.Month.ToString(); + } + + if (String.IsNullOrWhiteSpace(UsageYear)) + { + UsageYear = today.Year.ToString(); + } + + locale = System.Globalization.CultureInfo.CurrentCulture.ToString(); + + detailsUsage = CallClient(() => Client.Collections.GetUsageDetails(CollectionName, UsageYear, UsageMonth, locale), Client.Collections); + + if (detailsUsage == null) + { + return; + } + + // The request is async and we have to wait for the usage details to be produced here + do + { + System.Threading.Thread.Sleep(5000); + + operationResult = CallClient(() => Client.OperationResults.Get(detailsUsage.UsageDetails.OperationTrackingId), Client.OperationResults); + } + while(operationResult.RemoteAppOperationResult.Status != RemoteAppOperationStatus.Success || + operationResult.RemoteAppOperationResult.Status != RemoteAppOperationStatus.Failed || + --maxRetryCount > 0); + + if (operationResult.RemoteAppOperationResult.Status == RemoteAppOperationStatus.Success) + { + WriteUsageDetails(detailsUsage); + } + else + { + if (operationResult.RemoteAppOperationResult.Status == RemoteAppOperationStatus.Failed) + { + ErrorRecord error = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + Commands_RemoteApp.DetailedUsageFailureMessage, + String.Empty, + Client.Collections, + ErrorCategory.ResourceUnavailable); + + WriteError(error); + } + else if (maxRetryCount <= 0) + { + ErrorRecord error = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + Commands_RemoteApp.RequestTimedOut, + String.Empty, + Client.Collections, + ErrorCategory.OperationTimeout); + + WriteError(error); + } + } + } + + private void WriteUsageDetails(CollectionUsageDetailsResult detailsUsage) + { + // + // Display the content pointed to by the returned URI + // + WebResponse response = null; + + WebRequest request = WebRequest.Create(detailsUsage.UsageDetails.SasUri); + + try + { + response = (HttpWebResponse)request.GetResponse(); + } + catch (Exception e) + { + ErrorRecord error = RemoteAppCollectionErrorState.CreateErrorRecordFromException(e, String.Empty, Client.Collections, ErrorCategory.InvalidResult); + WriteError(error); + } + + using (Stream dataStream = response.GetResponseStream()) + { + using (StreamReader reader = new StreamReader(dataStream)) + { + String csvContent = reader.ReadToEnd(); + WriteObject(csvContent); + } + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/GetAzureRemoteAppCollectionUsageSummary.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/GetAzureRemoteAppCollectionUsageSummary.cs new file mode 100644 index 000000000000..8b8e565abbfa --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/GetAzureRemoteAppCollectionUsageSummary.cs @@ -0,0 +1,75 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppCollectionUsageSummary"), OutputType(typeof(BillingUsageSummary))] + public class GetAzureRemoteAppCollectionUsageSummary : RdsCmdlet + { + [Parameter(Mandatory = true, + Position = 0, + ValueFromPipelineByPropertyName = true, + HelpMessage = "RemoteApp collection name")] + public string CollectionName { get; set; } + + [Parameter(Mandatory = false, + Position = 1, + HelpMessage = "Number of the month (MM) to report usage")] + [ValidatePattern(TwoDigitMonthPattern)] + public string UsageMonth { get; set; } + + [Parameter(Mandatory = false, + Position = 2, + HelpMessage = "Year (YYYY) to report usage")] + [ValidatePattern(FullYearPattern)] + public string UsageYear { get; set; } + + public override void ExecuteCmdlet() + { + DateTime today = DateTime.Now; + + if (String.IsNullOrWhiteSpace(UsageMonth)) + { + UsageMonth = today.Month.ToString(); + } + + if (String.IsNullOrWhiteSpace(UsageYear)) + { + UsageYear = today.Year.ToString(); + } + + CollectionUsageSummaryListResult usageSummary = CallClient(() => Client.Collections.GetUsageSummary(CollectionName, UsageYear, UsageMonth), Client.Collections); + + if (usageSummary != null) + { + if (usageSummary.UsageSummaryList.Count > 0) + { + WriteObject(usageSummary.UsageSummaryList, true); + } + else + { + WriteVerboseWithTimestamp("No usage found for the requested period."); + } + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/GetAzureRemoteAppLocation.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/GetAzureRemoteAppLocation.cs new file mode 100644 index 000000000000..6961d13414a3 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/GetAzureRemoteAppLocation.cs @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppLocation"), OutputType(typeof(Region))] + public class GetAzureRemoteAppLocation : RdsCmdlet + { + public override void ExecuteCmdlet() + { + RegionListResult response = null; + + response = CallClient(() => Client.Collections.RegionList(), Client.Collections); + + if (response != null && response.Regions.Count > 0) + { + WriteObject(response.Regions, true); + } + else + { + WriteVerboseWithTimestamp("No locations found."); + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/Model/Collections.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/Model/Collections.cs new file mode 100644 index 000000000000..d044cf28a07f --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/Model/Collections.cs @@ -0,0 +1,52 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; + +namespace LocalModels +{ + public class Collection : Microsoft.Azure.Management.RemoteApp.Models.Collection + { + public DateTime LastModifiedLocalTime { get; set; } + public Collection(Microsoft.Azure.Management.RemoteApp.Models.Collection col) + { + AdInfo = col.AdInfo; + BillingPlanName = col.BillingPlanName; + Type = col.Type; + CustomRdpProperty = col.CustomRdpProperty; + Description = col.Description; + DnsServers = col.DnsServers; + LastErrorCode = col.LastErrorCode; + LastModifiedTimeUtc = col.LastModifiedTimeUtc; + LastModifiedLocalTime = col.LastModifiedTimeUtc.ToLocalTime(); + MaxSessions = col.MaxSessions; + Mode = col.Mode; + Name = col.Name; + OfficeType = col.OfficeType; + ReadyForPublishing = col.ReadyForPublishing; + Region = col.Region; + SessionWarningThreshold = col.SessionWarningThreshold; + Status = col.Status; + SubnetName = col.SubnetName; + TemplateImageName = col.TemplateImageName; + TrialOnly = col.TrialOnly; + VnetName = String.IsNullOrWhiteSpace(col.VnetName) || col.VnetName.StartsWith ("simplevnet-") ? "" : col.VnetName; + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/NewAzureRemoteAppCollection.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/NewAzureRemoteAppCollection.cs new file mode 100644 index 000000000000..9a637ca85f5a --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/NewAzureRemoteAppCollection.cs @@ -0,0 +1,265 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + using Microsoft.Azure.Commands.RemoteApp; + using Microsoft.Azure.Management.RemoteApp; + using Microsoft.Azure.Management.RemoteApp.Models; + using Microsoft.WindowsAzure.Management.Network; + using Microsoft.WindowsAzure.Management.Network.Models; + using System; + using System.Collections.Generic; + using System.Management.Automation; + using System.Net; + using System.Threading.Tasks; + + [Cmdlet(VerbsCommon.New, "AzureRemoteAppCollection", DefaultParameterSetName = NoDomain), OutputType(typeof(TrackingResult))] + public class NewAzureRemoteAppCollection : RdsCmdlet + { + private const string DomainJoined = "DomainJoined"; + private const string NoDomain = "NoDomain"; + + [Parameter (Mandatory = true, + Position = 0, + HelpMessage = "RemoteApp collection name")] + [ValidatePattern (NameValidatorString)] + public string CollectionName { get; set; } + + [Parameter(Mandatory = true, + Position = 1, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The name of the RemoteApp template image." + )] + public string ImageName { get; set; } + + [Parameter(Mandatory = true, + Position = 2, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Plan to use for this collection. Use Get-AzureRemoteAppPlan to see the plans available." + )] + public string Plan { get; set; } + + [Parameter(Mandatory = true, + Position = 3, + ValueFromPipelineByPropertyName = true, + ParameterSetName = NoDomain, + HelpMessage = "Location in which this collection will be created. Use Get-AzureRemoteAppLocation to see the locations available." + )] + public string Location { get; set; } + + [Parameter(Mandatory = true, + Position = 3, + ValueFromPipelineByPropertyName = true, + ParameterSetName = DomainJoined, + HelpMessage = "The name of the RemoteApp or Azure VNet to create the collection in." + )] + public string VNetName { get; set; } + + [Parameter(Mandatory = false, + ValueFromPipelineByPropertyName = true, + ParameterSetName = DomainJoined, + HelpMessage = "For Azure VNets only, a comma-separated list of DNS servers for the VNet." + )] + [ValidateNotNullOrEmpty] + public string DnsServers { get; set; } + + [Parameter(Mandatory = false, + ValueFromPipelineByPropertyName = true, + ParameterSetName = DomainJoined, + HelpMessage = "For Azure VNets only, the name of the subnet." + )] + [ValidateNotNullOrEmpty] + public string SubnetName { get; set; } + + [Parameter(Mandatory = true, + Position = 4, + ValueFromPipelineByPropertyName = true, + ParameterSetName = DomainJoined, + HelpMessage = "The name of the on-premise domain to join the RD Session Host servers to." + )] + [ValidatePattern(DomainNameValidatorString)] + public string Domain { get; set; } + + [Parameter(Mandatory = true, + Position = 5, + ValueFromPipelineByPropertyName = true, + ParameterSetName = DomainJoined, + HelpMessage = "The users credentials that has permission to add computers to the domain." + )] + public PSCredential Credential { get; set; } + + [Parameter(Mandatory = false, + ValueFromPipelineByPropertyName = true, + ParameterSetName = DomainJoined, + HelpMessage = "The name of your organizational unit to join the RD Session Host servers, e.g. OU=MyOu,DC=MyDomain,DC=ParentDomain,DC=com. Attributes such as OU, DC, etc. must be in uppercase." + )] + [ValidatePattern(OrgIDValidatorString)] + public string OrganizationalUnit { get; set; } + + [Parameter(Mandatory = false, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Description of what this collection is used for." + )] + [ValidateNotNullOrEmpty] + public string Description { get; set; } + + [Parameter(Mandatory = false, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Used to allow RDP redirection." + )] + [ValidateNotNullOrEmpty] + public string CustomRdpProperty { get; set; } + + public override void ExecuteCmdlet() + { + // register the subscription for this service if it has not been before + // sebsequent call to register is redundent + RegisterSubscriptionWithRdfeForRemoteApp(); + + NetworkCredential creds = null; + CollectionCreationDetails details = new CollectionCreationDetails() + { + Name = CollectionName, + TemplateImageName = ImageName, + Region = Location, + BillingPlanName = Plan, + Description = Description, + CustomRdpProperty = CustomRdpProperty, + Mode = CollectionMode.Apps + }; + OperationResultWithTrackingId response = null; + + switch (ParameterSetName) + { + case DomainJoined: + { + creds = Credential.GetNetworkCredential(); + details.VnetName = VNetName; + + if (SubnetName != null && DnsServers != null) + { + if (!IsFeatureEnabled(EnabledFeatures.azureVNet)) + { + ErrorRecord er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + string.Format(Commands_RemoteApp.LinkAzureVNetFeatureNotEnabledMessage), + String.Empty, + Client.Account, + ErrorCategory.InvalidOperation + ); + + ThrowTerminatingError(er); + } + + details.SubnetName = SubnetName; + details.DnsServers = DnsServers.Split(new char[] { ',' }); + + ValidateCustomerVNetParams(details.VnetName, details.SubnetName, details.DnsServers); + + details.Region = Location; + } + + details.AdInfo = new ActiveDirectoryConfig() + { + DomainName = Domain, + OrganizationalUnit = OrganizationalUnit, + UserName = creds.UserName, + Password = creds.Password, + }; + break; + } + case NoDomain: + default: + { + details.Region = Location; + break; + } + } + + response = CallClient(() => Client.Collections.Create(false, details), Client.Collections); + + if (response != null) + { + TrackingResult trackingId = new TrackingResult(response); + WriteObject(trackingId); + } + } + + private bool ValidateCustomerVNetParams(string name, string subnet, IEnumerable dns) + { + NetworkListResponse.VirtualNetworkSite azureVNet = GetAzureVNet(name); + bool isValidSubnetName = false; + + if (azureVNet == null) + { + ErrorRecord er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + String.Format(Commands_RemoteApp.InvalidArgumentVNetNameNotFoundMessageFormat, name), + String.Empty, + Client.Collections, + ErrorCategory.InvalidArgument + ); + + ThrowTerminatingError(er); + } + + foreach (NetworkListResponse.Subnet azureSubnet in azureVNet.Subnets) + { + if (string.Compare(azureSubnet.Name, subnet, true) == 0) + { + isValidSubnetName = true; + + Location = azureVNet.Location; + break; + } + } + + if (!isValidSubnetName) + { + ErrorRecord er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + String.Format(Commands_RemoteApp.InvalidArgumentSubNetNameNotFoundMessageFormat, subnet), + String.Empty, + Client.Collections, + ErrorCategory.InvalidArgument + ); + + ThrowTerminatingError(er); + } + + return isValidSubnetName; + } + + private NetworkListResponse.VirtualNetworkSite GetAzureVNet(string name) + { + NetworkManagementClient networkClient = new NetworkManagementClient(this.Client.Credentials, this.Client.BaseUri); + Task listNetworkTask = networkClient.Networks.ListAsync(); + + listNetworkTask.Wait(); + + if (listNetworkTask.Status == TaskStatus.RanToCompletion) + { + NetworkListResponse networkList = listNetworkTask.Result; + + foreach (NetworkListResponse.VirtualNetworkSite network in networkList.VirtualNetworkSites) + { + if (network.Name == name) + { + return network; + } + } + } + + return null; + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/RemoveAzureRemoteAppCollection.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/RemoveAzureRemoteAppCollection.cs new file mode 100644 index 000000000000..5ab23f0b0ec3 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/RemoveAzureRemoteAppCollection.cs @@ -0,0 +1,44 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Remove, "AzureRemoteAppCollection", SupportsShouldProcess = true), OutputType(typeof(TrackingResult))] + public class RemoveAzureRemoteAppCollection : RdsCmdlet + { + [Parameter(Mandatory = true, + Position = 0, + HelpMessage = "RemoteApp collection name")] + [ValidatePattern(NameValidatorString)] + public string CollectionName { get; set; } + + public override void ExecuteCmdlet() + { + OperationResultWithTrackingId response = null; + + if (ShouldProcess(CollectionName, "Remove collection")) + { + response = CallClient(() => Client.Collections.Delete(CollectionName), Client.Collections); + if (response != null) + { + WriteTrackingId(response); + } + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/SetAzureRemoteAppCollection.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/SetAzureRemoteAppCollection.cs new file mode 100644 index 000000000000..1250c3978f4d --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/SetAzureRemoteAppCollection.cs @@ -0,0 +1,122 @@ +// ---------------------------------------------------------------------------------- +// +// 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.RemoteApp; +using Microsoft.Azure.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.Management.Automation; +using System.Net; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Set, "AzureRemoteAppCollection"), OutputType(typeof(TrackingResult))] + public class SetAzureRemoteAppCollection : RdsCmdlet + { + private const string DomainJoined = "DomainJoined"; + private const string NoDomain = "NoDomain"; + + [Parameter(Mandatory = true, + Position = 0, + HelpMessage = "RemoteApp collection name")] + [ValidatePattern(NameValidatorString)] + public string CollectionName { get; set; } + + [Parameter(Mandatory = false, + Position = 1, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Plan to use for this collection. Use Get-AzureRemoteAppPlan to see the plans available.")] + [ValidateNotNullOrEmpty] + public string Plan { get; set; } + + [Parameter(Mandatory = false, + Position = 2, + ValueFromPipelineByPropertyName = true, + ParameterSetName = DomainJoined, + HelpMessage = "Credentials of a user that has permission to add computers to the domain.")] + [ValidateNotNull] + public PSCredential Credential { get; set; } + + [Parameter(Mandatory = false, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Description of what this collection is used for.")] + [ValidateNotNullOrEmpty] + public string Description { get; set; } + + [Parameter(Mandatory = false, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Used to allow RDP redirection.")] + [ValidateNotNullOrEmpty] + public string CustomRdpProperty { get; set; } + + public override void ExecuteCmdlet() + { + NetworkCredential creds = null; + CollectionCreationDetails details = null; + OperationResultWithTrackingId response = null; + Collection collection = null; + + collection = FindCollection(CollectionName); + if (collection == null) + { + return; + } + + details = new CollectionCreationDetails() + { + Name = CollectionName, + BillingPlanName = String.IsNullOrWhiteSpace(Plan) ? collection.BillingPlanName : Plan, + Description = String.IsNullOrWhiteSpace(Description) ? collection.Description : Description, + CustomRdpProperty = String.IsNullOrWhiteSpace(CustomRdpProperty) ? collection.CustomRdpProperty : CustomRdpProperty, + TemplateImageName = collection.TemplateImageName + }; + + switch (ParameterSetName) + { + case DomainJoined: + { + if (collection.AdInfo == null) + { + ErrorRecord er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + Commands_RemoteApp.AadInfoCanNotBeAddedToCloudOnlyCollectionMessage, + String.Empty, + Client.Collections, + ErrorCategory.InvalidArgument); + ThrowTerminatingError(er); + } + + details.AdInfo = new ActiveDirectoryConfig(); + details.VnetName = collection.VnetName; + details.AdInfo.DomainName = collection.AdInfo.DomainName; + details.AdInfo.OrganizationalUnit = collection.AdInfo.OrganizationalUnit; + + if (Credential != null) + { + creds = Credential.GetNetworkCredential(); + details.AdInfo.UserName = creds.UserName; + details.AdInfo.Password = creds.Password; + } + break; + } + } + + response = CallClient(() => Client.Collections.Set(CollectionName, false, false, details), Client.Collections); + if (response != null) + { + TrackingResult trackingId = new TrackingResult(response); + WriteObject(trackingId); + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/UpdateAzureRemoteAppCollection.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/UpdateAzureRemoteAppCollection.cs new file mode 100644 index 000000000000..358305697e98 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Collection/UpdateAzureRemoteAppCollection.cs @@ -0,0 +1,68 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsData.Update, "AzureRemoteAppCollection", SupportsShouldProcess = true), OutputType(typeof(TrackingResult))] + + public class UpdateAzureRemoteAppCollection : RdsCmdlet + { + [Parameter (Mandatory = true, + Position = 0, + HelpMessage = "RemoteApp collection name")] + [ValidatePattern (NameValidatorString)] + public string CollectionName { get; set; } + + + [Parameter(Mandatory = true, + Position = 1, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The name of the RemoteApp template image." + )] + public string ImageName { get; set; } + + public override void ExecuteCmdlet() + { + CollectionCreationDetails details = null; + OperationResultWithTrackingId response = null; + Collection collection = null; + + collection = FindCollection(CollectionName); + + if (collection != null) + { + details = new CollectionCreationDetails() + { + Name = CollectionName, + TemplateImageName = ImageName, + BillingPlanName = collection.BillingPlanName + }; + + if (ShouldProcess(CollectionName, "Update collection")) + { + response = CallClient(() => Client.Collections.Set(CollectionName, false, false, details), Client.Collections); + } + + if (response != null) + { + WriteTrackingId(response); + } + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Commands.RemoteApp.Designer.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Commands.RemoteApp.Designer.cs new file mode 100644 index 000000000000..c7124fcbf63b --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Commands.RemoteApp.Designer.cs @@ -0,0 +1,198 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34014 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Azure.Commands.RemoteApp { + 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 Commands_RemoteApp { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Commands_RemoteApp() { + } + + /// + /// 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.RemoteApp.Commands.RemoteApp", typeof(Commands_RemoteApp).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 AdInfo cannot be added to a ClouldOnly Collection. + /// + internal static string AadInfoCanNotBeAddedToCloudOnlyCollectionMessage { + get { + return ResourceManager.GetString("AadInfoCanNotBeAddedToCloudOnlyCollectionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to RemoteApp collection name: {0} not found. + /// + internal static string CollectionNotFoundByNameFormat { + get { + return ResourceManager.GetString("CollectionNotFoundByNameFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to generate the detailed usage informaton. Please try again and if it still does not succeed, then call Microsoft support.. + /// + internal static string DetailedUsageFailureMessage { + get { + return ResourceManager.GetString("DetailedUsageFailureMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Are you sure?. + /// + internal static string GenericAreYouSureQuestion { + get { + return ResourceManager.GetString("GenericAreYouSureQuestion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid Argument SubnetName: {0} not found. + /// + internal static string InvalidArgumentSubNetNameNotFoundMessageFormat { + get { + return ResourceManager.GetString("InvalidArgumentSubNetNameNotFoundMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid Argument VNetName: {0} not found. + /// + internal static string InvalidArgumentVNetNameNotFoundMessageFormat { + get { + return ResourceManager.GetString("InvalidArgumentVNetNameNotFoundMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to "Link Azure VNet" Feature not enabled. + /// + internal static string LinkAzureVNetFeatureNotEnabledMessage { + get { + return ResourceManager.GetString("LinkAzureVNetFeatureNotEnabledMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No plans found.. + /// + internal static string NoPlansFoundMessage { + get { + return ResourceManager.GetString("NoPlansFoundMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request timed out. + /// + internal static string RequestTimedOut { + get { + return ResourceManager.GetString("RequestTimedOut", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Logging off user session.... + /// + internal static string SessionLogOffCaptionMessage { + get { + return ResourceManager.GetString("SessionLogOffCaptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This operation will log off {0}. Are you sure?. + /// + internal static string SessionLogOffWarningQuestionFormat { + get { + return ResourceManager.GetString("SessionLogOffWarningQuestionFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This operation will unpublish all applications for this collection.. + /// + internal static string UnpublishProgramCaptionMessage { + get { + return ResourceManager.GetString("UnpublishProgramCaptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unpublishing all applications. + /// + internal static string UnpublishProgramConfirmationDescription { + get { + return ResourceManager.GetString("UnpublishProgramConfirmationDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This operation will reset the shared key for the VNet's VPN device. This will interrupt connectivity to the on-premises network until you configure the VPN device to use the new shared key.. + /// + internal static string VnetSharedKeyResetCaptionMessage { + get { + return ResourceManager.GetString("VnetSharedKeyResetCaptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Resetting the VPN shared key. + /// + internal static string VnetSharedKeyResetConfirmationDescription { + get { + return ResourceManager.GetString("VnetSharedKeyResetConfirmationDescription", resourceCulture); + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Commands.RemoteApp.csproj b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Commands.RemoteApp.csproj new file mode 100644 index 000000000000..c9be84045394 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Commands.RemoteApp.csproj @@ -0,0 +1,237 @@ + + + + + Debug + AnyCPU + {492D2AF2-950B-4F2E-8079-8794305313FD} + Library + ..\..\..\Package\$(Configuration)\ServiceManagement\Azure\RemoteApp\ + Properties + Microsoft.Azure.Commands.RemoteApp + Microsoft.Azure.Commands.RemoteApp + v4.5 + 512 + ..\..\ + true + + b0795d32 + + + AnyCPU + true + full + false + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + TRACE;SIGN + prompt + 4 + MinimumRecommendedRules.ruleset + ;$(ProgramFiles)\Microsoft Visual Studio 12.0\Team Tools\Static Analysis Tools\Rule Sets + ;$(ProgramFiles)\Microsoft Visual Studio 12.0\Team Tools\Static Analysis Tools\FxCop\Rules + true + true + MSSharedLibKey.snk + true + false + + + + + + + + Designer + + + + + False + ..\..\..\packages\Hyak.Common.1.0.2\lib\net45\Hyak.Common.dll + + + False + ..\..\..\packages\Microsoft.Azure.Common.2.0.3\lib\net45\Microsoft.Azure.Common.dll + + + False + ..\..\..\packages\Microsoft.Azure.Common.Authentication.1.0.13-preview\lib\net45\Microsoft.Azure.Common.Authentication.dll + + + False + ..\..\..\packages\Microsoft.Azure.Common.2.0.3\lib\net45\Microsoft.Azure.Common.NetFramework.dll + + + False + ..\..\..\packages\Microsoft.Azure.Management.RemoteApp.1.0.7\lib\net40\Microsoft.Azure.Management.RemoteApp.dll + + + ..\..\..\packages\Microsoft.Data.Edm.5.6.0\lib\net40\Microsoft.Data.Edm.dll + + + ..\..\..\packages\Microsoft.Data.OData.5.6.0\lib\net40\Microsoft.Data.OData.dll + + + ..\..\..\packages\Microsoft.Data.Services.Client.5.6.0\lib\net40\Microsoft.Data.Services.Client.dll + + + False + ..\..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.12.111071459\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + + ..\..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.12.111071459\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll + + + ..\..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll + + + ..\..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll + + + ..\..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll + + + False + ..\..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.1.8.0.0\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll + + + False + ..\..\..\packages\Microsoft.WindowsAzure.Management.4.0.1\lib\net40\Microsoft.WindowsAzure.Management.dll + + + False + ..\..\..\packages\Microsoft.WindowsAzure.Management.Compute.9.2.0\lib\net40\Microsoft.WindowsAzure.Management.Compute.dll + + + False + ..\..\..\packages\Microsoft.WindowsAzure.Management.Network.6.1.1\lib\net40\Microsoft.WindowsAzure.Management.Network.dll + + + False + ..\..\..\packages\Microsoft.WindowsAzure.Management.Storage.5.0.0\lib\net40\Microsoft.WindowsAzure.Management.Storage.dll + + + False + ..\..\..\packages\WindowsAzure.Storage.4.0.0\lib\net40\Microsoft.WindowsAzure.Storage.dll + + + False + ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + + + + + + + + False + ..\..\..\packages\Microsoft.Net.Http.2.2.28\lib\net45\System.Net.Http.Extensions.dll + + + False + ..\..\..\packages\Microsoft.Net.Http.2.2.28\lib\net45\System.Net.Http.Primitives.dll + + + + + ..\..\..\packages\System.Spatial.5.6.0\lib\net40\System.Spatial.dll + + + + + + + + + + + {5ee72c53-1720-4309-b54b-5fb79703195f} + Commands.Common + + + + + + + + + + + + + + + True + True + Commands.RemoteApp.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Commands.RemoteApp.Designer.cs + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + \ No newline at end of file diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Commands.RemoteApp.resx b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Commands.RemoteApp.resx new file mode 100644 index 000000000000..82eae7ff15ad --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Commands.RemoteApp.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + AdInfo cannot be added to a ClouldOnly Collection + + + RemoteApp collection name: {0} not found + + + Failed to generate the detailed usage informaton. Please try again and if it still does not succeed, then call Microsoft support. + + + Are you sure? + + + Invalid Argument SubnetName: {0} not found + + + Invalid Argument VNetName: {0} not found + + + "Link Azure VNet" Feature not enabled + + + No plans found. + + + Request timed out + + + Logging off user session... + + + This operation will log off {0}. Are you sure? + + + This operation will unpublish all applications for this collection. + + + Unpublishing all applications + + + This operation will reset the shared key for the VNet's VPN device. This will interrupt connectivity to the on-premises network until you configure the VPN device to use the new shared key. + + + Resetting the VPN shared key + + \ No newline at end of file diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/CmdRuntime.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/CmdRuntime.cs new file mode 100644 index 000000000000..9b698466fea7 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/CmdRuntime.cs @@ -0,0 +1,72 @@ +// ---------------------------------------------------------------------------------- +// +// 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 System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + public abstract partial class RdsCmdlet : AzurePSCmdlet + { + + public new void WriteObject(object sendToPipeline) + { + if (theJob != null) + { + theJob.Output.Add(PSObject.AsPSObject(sendToPipeline)); + } + else + { + this.CommandRuntime.WriteObject(sendToPipeline); + } + } + + public new void WriteError(ErrorRecord errorRecord) + { + if (theJob != null) + { + theJob.Error.Add(errorRecord); + } + else + { + this.CommandRuntime.WriteError(errorRecord); + } + } + + public new void WriteVerbose(string text) + { + if (theJob != null) + { + theJob.Verbose.Add(new VerboseRecord(text)); + } + else + { + this.CommandRuntime.WriteVerbose(text); + } + } + + public new void WriteWarning(string text) + { + if (theJob != null) + { + theJob.Warning.Add(new WarningRecord(text)); + } + else + { + this.CommandRuntime.WriteWarning(text); + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/Exception.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/Exception.cs new file mode 100644 index 000000000000..60cf88ada9b2 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/Exception.cs @@ -0,0 +1,208 @@ +// ---------------------------------------------------------------------------------- +// +// 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.WindowsAzure; +using System; +using System.Management.Automation; +using System.Net; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + public enum ExceptionType + { + NonTerminating = 0, + Terminating = 1 + } + + public class ErrorRecordState + { + public HttpStatusCode Status; + public string ExceptionMessage; + public ErrorCategory Category; + public ExceptionType type; + } + + public class CloudRecordState + { + public ErrorRecordState state; + public ErrorRecord er; + } + + public class RemoteAppServiceException : Exception + { + internal ErrorCategory category { get; private set; } + + public RemoteAppServiceException(String message, ErrorCategory cat) + : base(message) + { + category = cat; + } + } + + + public class RemoteAppCollectionErrorState + { + internal static readonly ErrorRecordState[] ErrorRecordStateTable = new ErrorRecordState[] + { + new ErrorRecordState() + { + Status = HttpStatusCode.BadRequest, + ExceptionMessage = "Invalid argument", + Category = ErrorCategory.InvalidArgument, + }, + new ErrorRecordState() + { + Status = HttpStatusCode.Unauthorized, + ExceptionMessage = "Authentication error", + Category = ErrorCategory.AuthenticationError, + }, + new ErrorRecordState() + { + Status = HttpStatusCode.Forbidden, + ExceptionMessage = "Security error", + Category = ErrorCategory.SecurityError, + }, + new ErrorRecordState() + { + Status = HttpStatusCode.RequestTimeout, + ExceptionMessage = "Timeout error", + Category = ErrorCategory.OperationTimeout, + }, + new ErrorRecordState() + { + Status = HttpStatusCode.ProxyAuthenticationRequired, + ExceptionMessage = "Proxy authentication error", + Category = ErrorCategory.AuthenticationError, + }, + new ErrorRecordState() + { + Status = HttpStatusCode.NotFound, + ExceptionMessage = "Resource not found", + Category = ErrorCategory.ObjectNotFound, + }, + new ErrorRecordState() + { + Status = HttpStatusCode.Gone, + ExceptionMessage = "Resource not found", + Category = ErrorCategory.ObjectNotFound, + }, + new ErrorRecordState() + { + Status = HttpStatusCode.InternalServerError, + ExceptionMessage = "Server error", + Category = ErrorCategory.InvalidOperation, + }, + new ErrorRecordState() + { + Status = HttpStatusCode.BadGateway, + ExceptionMessage = "Gateway error", + Category = ErrorCategory.InvalidOperation, + }, + new ErrorRecordState() + { + Status = HttpStatusCode.ServiceUnavailable, + ExceptionMessage = "Service Unavailable", + Category = ErrorCategory.OperationStopped, + }, + new ErrorRecordState() + { + Status = HttpStatusCode.GatewayTimeout, + ExceptionMessage = "Timeout error", + Category = ErrorCategory.OperationTimeout, + } + }; + + internal static readonly ErrorRecordState erDefault = new ErrorRecordState() + { + Status = HttpStatusCode.Unused, + ExceptionMessage = "Unknown error", + Category = ErrorCategory.NotSpecified, + }; + + internal static CloudRecordState CreateErrorStateFromCloudException(CloudException e, string errorId, object targetObject) + { + ErrorRecordState state = CreateErrorStateFromHttpStatusCode(e.Response.StatusCode); + ErrorRecord er = RemoteAppCollectionErrorState.CreateErrorRecordFromException(e, errorId, targetObject, state.Category); + CloudRecordState cloudRecord = new CloudRecordState() + { + state = state, + er = er + }; + + return cloudRecord; + } + + internal static ErrorRecord CreateErrorRecordFromException(Exception e, string errorId, object targetObject, ErrorCategory category) + { + ErrorRecord er = new ErrorRecord(e, errorId, category, targetObject.GetType().Name); + return er; + } + + internal static ErrorRecord CreateErrorRecordFromString(string errorMessage, string errorId, object targetObject, ErrorCategory category) + { + string ExceptionMessage = String.Format("{0:T} - {1}", + DateTime.Now, + errorMessage); + + Exception ex = new Exception(ExceptionMessage); + + ErrorRecord er = CreateErrorRecordFromException(ex, errorId, targetObject, category); + + return er; + } + + internal static ErrorRecordState CreateErrorStateFromHttpStatusCode(HttpStatusCode status) + { + ErrorRecordState erStateToUse = erDefault; + + foreach (ErrorRecordState recordState in ErrorRecordStateTable) + { + if (status == recordState.Status) + { + erStateToUse = recordState; + break; + } + } + + string exceptionMessage = String.Format("{0:T} - {1} in call to server HTTP Status: {2}", + DateTime.Now, + erStateToUse.ExceptionMessage, + status); + + ErrorRecordState state = new ErrorRecordState() + { + Status = erStateToUse.Status, + ExceptionMessage = exceptionMessage, + Category = erStateToUse.Category + }; + + if (state.Status == HttpStatusCode.BadRequest || + state.Status == HttpStatusCode.RequestTimeout || + state.Status == HttpStatusCode.NotFound || + state.Status == HttpStatusCode.GatewayTimeout) + { + state.type = ExceptionType.NonTerminating; + } + else + { + state.type = ExceptionType.Terminating; + } + + return state; + } + } +} + diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/LongRunningTask.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/LongRunningTask.cs new file mode 100644 index 000000000000..614f2fb86888 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/LongRunningTask.cs @@ -0,0 +1,140 @@ +// ---------------------------------------------------------------------------------- +// +// 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 System; +using System.Management.Automation; +using System.Threading; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + internal class LongRunningTask : Job where T : RdsCmdlet + { + private string statusMessage; + + private string joblocation; + + private JobStateInfo jobstate; + + private string description; + + private delegate void WaitCallback(Action task); + + private T cmdlet; + + public LongRunningTask(T command, string JobName, string Description) + { + statusMessage = null; + joblocation = null; + jobstate = null; + Name = JobName; + description = Description; + SetState(JobState.NotStarted, null); + SetLocation("localhost"); + cmdlet = command; + } + + public override string StatusMessage + { + get + { + return statusMessage; + } + } + + public override bool HasMoreData + { + get + { + return Output.Count > 0 || Warning.Count > 0 || Error.Count > 0 || Verbose.Count > 0; + } + } + + public override string Location + { + get + { + return joblocation; + } + } + + public new JobStateInfo JobStateInfo + { + get + { + return jobstate; + } + } + + public override void StopJob() + { + throw new NotImplementedException(); + } + + internal void SetLocation(string location) + { + joblocation = location; + } + + internal void SetStatus(string status) + { + statusMessage = status; + } + + internal void SetState(JobState state, Exception reason) + { + SetJobState(state); + jobstate = new JobStateInfo(state, reason); + } + + internal void ProcessJob(Action task) + { + cmdlet.JobRepository.Add(this); + + if (ThreadPool.QueueUserWorkItem(t => DoProcessLogic((Action)t), task) == false) + { + throw new RemoteAppServiceException("Failed to create job", ErrorCategory.InvalidOperation); + } + } + + protected virtual void DoProcessLogic(Action task) + { + JobState state = JobState.Running; + string title = cmdlet.Host.UI.RawUI.WindowTitle; + cmdlet.Host.UI.RawUI.WindowTitle = description; + SetState(state, null); + RdsCmdlet.theJob = this; + + try + { + task.Invoke(); + state = Error.Count > 0 ? JobState.Failed : JobState.Completed; + SetState(state, null); + } + catch (Exception e) + { + SetState(JobState.Failed, e); + ErrorRecord er = new ErrorRecord(e, Name, ErrorCategory.InvalidOperation, null); + cmdlet.WriteError(er); + } + + + if (state == JobState.Completed) + { + SetStatus(""); + } + cmdlet.Host.UI.RawUI.WindowTitle = title; + RdsCmdlet.theJob = null; + } + } +} \ No newline at end of file diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/RdsCmdlet.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/RdsCmdlet.cs new file mode 100644 index 000000000000..0670fbcd0c29 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/RdsCmdlet.cs @@ -0,0 +1,338 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Models; +using Microsoft.Azure.Management.RemoteApp.Models; +using Microsoft.WindowsAzure.Commands.Utilities.Common; +using System; +using System.Collections.ObjectModel; +using System.Management.Automation; +using System.Security.Principal; + + +namespace Microsoft.Azure.Management.RemoteApp.Models +{ + + public class TrackingResult + { + public string TrackingId { get; set; } + + public TrackingResult(OperationResultWithTrackingId operation) + { + TrackingId = operation.TrackingId; + } + } +} + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + public class EnabledFeatures + { + public const string azureVNet = "AzureVNet"; + public const string goldImageImport = "GoldImageImport"; + } + + public abstract partial class RdsCmdlet : AzurePSCmdlet + { + [ThreadStatic] + internal static Job theJob; + + private IRemoteAppManagementClient client = null; + + public IRemoteAppManagementClient Client + { + get + { + if (client == null) + { + client = AzureSession.ClientFactory.CreateClient(Profile.Context, AzureEnvironment.Endpoint.ServiceManagement); + client.RdfeNamespace = "remoteapp"; + + // Read the namespace if defined as an environment variable from the session configuration + string rdfeNamespace = Environment.GetEnvironmentVariable("rdfeNamespace"); + + if (!string.IsNullOrWhiteSpace(rdfeNamespace)) + { + client.RdfeNamespace = rdfeNamespace; + } + } + + return client; + } + + set + { + client = value; // Test Hook + } + } + + protected WildcardPattern Wildcard { get; set; } + + protected bool UseWildcard + { + get { return Wildcard != null; } + } + + protected bool ExactMatch { get; set;} + + public RdsCmdlet() + { + Wildcard = null; + ExactMatch = false; + theJob = null; + } + + internal void VerifySessionIsElevated() + { + WindowsIdentity identity = WindowsIdentity.GetCurrent(); + WindowsPrincipal securityPrincipal = new WindowsPrincipal(identity); + WindowsBuiltInRole Admin = WindowsBuiltInRole.Administrator; + + if (securityPrincipal.IsInRole(Admin) == false) + { + throw new RemoteAppServiceException("This cmdlet must be run in an elevated Powershell session", ErrorCategory.InvalidOperation); + } + + } + + internal Collection CallPowershell(string script) + { + Collection pipeLineObjects = null; + + System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create(); + ps.AddScript(script); + + pipeLineObjects = ps.Invoke(); + + if (ps.HadErrors) + { + throw ps.Streams.Error[0].Exception; + } + + return pipeLineObjects; + } + + internal Collection CallPowershellWithReturnType(string script) + { + Collection pipeLineObjects = null; + Collection result = new Collection(); + + System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create(); + ps.AddScript(script); + + pipeLineObjects = ps.Invoke(); + + if (ps.HadErrors) + { + throw ps.Streams.Error[0].Exception; + } + + foreach (PSObject obj in pipeLineObjects) + { + T item = LanguagePrimitives.ConvertTo(obj); + result.Add(item); + } + + return result; + } + + protected void CreateWildcardPattern(string name) + { + try + { + if (!String.IsNullOrWhiteSpace(name)) + { + ExactMatch = !WildcardPattern.ContainsWildcardCharacters(name); + + Wildcard = new WildcardPattern(name, WildcardOptions.IgnoreCase); + } + } + catch (WildcardPatternException e) + { + ErrorRecord er = new ErrorRecord(e, "", ErrorCategory.InvalidArgument, Wildcard); + ThrowTerminatingError(er); + } + } + + protected Collection FindCollection(string CollectionName) + { + CollectionResult response = null; + + response = CallClient(() => Client.Collections.Get(CollectionName), Client.Collections); + + if (response != null) + { + if (response.Collection == null) + { + ErrorRecord er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + String.Format("Collection {0} does not exist", + CollectionName), + String.Empty, + Client.Principals, + ErrorCategory.ObjectNotFound + ); + + WriteError(er); + } + else if (!String.Equals(response.Collection.Status, "Active", StringComparison.OrdinalIgnoreCase)) + { + ErrorRecord er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + String.Format("Collection {0} is not in the Active state", + response.Collection.Name), + String.Empty, + Client.Principals, + ErrorCategory.InvalidOperation + ); + + WriteError(er); + } + } + + return response.Collection; + } + + protected void RegisterSubscriptionWithRdfeForRemoteApp() + { + System.Threading.CancellationToken cancelationToken = new System.Threading.CancellationToken(); + + // register the subscription with RDFE to use the RemoteApp resource + Microsoft.WindowsAzure.Management.ManagementClient mgmtClient = + AzureSession.ClientFactory.CreateClient(Profile.Context, AzureEnvironment.Endpoint.ServiceManagement); + + try + { + AzureOperationResponse azureOperationResponse = mgmtClient.Subscriptions.RegisterResourceAsync(Client.RdfeNamespace, cancelationToken).Result; + } + catch (Exception e) + { + // Handle if this or the inner exception is of type CloudException + CloudException ce = e as CloudException; + + if (ce == null) + { + ce = e.InnerException as CloudException; + } + + if (ce != null) + { + // ignore the 'ConflictError' which is returned if the subscription is already registered for the resource + if (ce.Error.Code != "ConflictError") + { + HandleCloudException(mgmtClient.Subscriptions, ce); + } + } + else + { + ErrorRecord er = RemoteAppCollectionErrorState.CreateErrorRecordFromException(e, String.Empty, mgmtClient.Subscriptions, ErrorCategory.NotSpecified); + + ThrowTerminatingError(er); + } + } + + } + + protected T CallClient(Func func, object targetObject) where T : AzureOperationResponse + { + T response = default(T); + + try + { + response = func(); + } + catch (Exception e) + { + // Handle if this or the inner exception is of type CloudException + CloudException ce = e as CloudException; + ErrorRecord er = null; + + if (ce == null) + { + ce = e.InnerException as CloudException; + } + + if (ce != null) + { + HandleCloudException(targetObject, ce); + } + else + { + er = RemoteAppCollectionErrorState.CreateErrorRecordFromException(e, String.Empty, targetObject, ErrorCategory.NotSpecified); + + ThrowTerminatingError(er); + } + } + + return response; + } + + private void HandleCloudException(object targetObject, CloudException e) + { + CloudRecordState cloudRecord = RemoteAppCollectionErrorState.CreateErrorStateFromCloudException(e, String.Empty, targetObject); + if (cloudRecord.state.type == ExceptionType.NonTerminating) + { + WriteError(cloudRecord.er); + } + else + { + ThrowTerminatingError(cloudRecord.er); + } + } + + protected T CallClient_ThrowOnError(Func func) where T : AzureOperationResponse + { + T response = default(T); + + response = func(); + + return response; + } + + protected void WriteTrackingId(OperationResultWithTrackingId response) + { + + WriteVerboseWithTimestamp("Please use the following tracking id with Get-AzureRemoteAppOperationResult cmdlet:"); + TrackingResult trackingId = new TrackingResult(response); + WriteObject(trackingId); + } + + public bool IsFeatureEnabled(string featureName) + { + EnabledFeaturesResult features = Client.Account.GetEnabledFeatures(); + + if (features.StatusCode != System.Net.HttpStatusCode.OK) + { + ErrorRecord er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + string.Format("Failed to enumerate enabled features"), + String.Empty, + Client.Account, + ErrorCategory.ConnectionError + ); + + ThrowTerminatingError(er); + } + + foreach (string feature in features.EnabledFeatures) + { + if (string.Compare(feature, featureName, true) == 0) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/RemoteAppRegEx.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/RemoteAppRegEx.cs new file mode 100644 index 000000000000..dd44f3b4c166 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Common/RemoteAppRegEx.cs @@ -0,0 +1,148 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + public abstract partial class RdsCmdlet + { + protected const string NameValidatorStringWithWildCards = @"^[?*A-Za-z0-9\u007F-\uFFFF]{1,12}$"; + + protected const string NameValidatorString = @"^[A-Za-z][A-Za-z0-9\u007F-\uFFFF]{2,12}$"; + + protected const string VNetNameValidatorStringWithWildCards = @"^[?*A-Za-z][?*-A-Za-z0-9]{3,49}(?\s]+"; + + protected const string UserPrincipalValdatorString = UserNameValidatorString + "@" + DomainNameValidatorString; + + protected const string IPv4ValidatorString = @"^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$"; + + protected const string IPv6ValidatorString = @"(? = < > " # ; or + + protected const string normalCharRegexPattern = @"[A-Za-z0-9 !\$%&'()\*\-\.\/\:\?@\[\]\^_`{\|}~]"; + + // valid escape characters for non quoted string, , \ = < > " # ; or +, or an 8 bit value encoded as a hex pair + protected const string escapedCharRegexPattern = @"\\([ ,\\\r=<>""#;+]|[0-9A-Fa-f]{2})"; + + // ASCII code in hex, e.g. #ff + protected const string asciiCodeRegexPattern = @"#([0-9A-Fa-f]{2})+"; + + // used to separate entities, , or + followed by 0 or more spaces + protected const string delimiterRegexPattern = @"[\+,]\s*"; + + // an entity value, consists of normal chars, escape codes and hex codes + protected const string entityRegexPattern = @"(" + normalCharRegexPattern + @"|" + escapedCharRegexPattern + @"|" + asciiCodeRegexPattern + @")+"; + + // a key/entity pair, e.g DC=foo-bar.com + protected const string keyEntityRegexPattern = @"(" + keyRegexPattern + @"=" + entityRegexPattern + @")"; + + // an OU key/entity pair, e.g OU=MyOu, but only matches an OU entity + protected const string ouKeyEntityRegexPattern = @"(" + ouKeyRegexPattern + @"=" + entityRegexPattern + @")"; + + protected const string OrgIDValidatorString = @"^(" + keyEntityRegexPattern + @"[\+,]\s*)*" + ouKeyEntityRegexPattern + @"([\+,]\s*" + keyEntityRegexPattern + @")*$"; + + /* OrganizationalUnit in DN format: OU=MyOu, CN=MyDomain, CN=Com + * + * The following pattern is designed to accept an RFC 4514 compliant distinguished name wich contains at least 1 OU element + * e.g OU=MyOu, CN=MyDomain, CN=Com + * + * The pattern can be broken down into 3 main components + * + * The first component is 0 or more entities that are not OU entities, followed by a + or + delimiter + * ^( + * ( + * The entity begins with a key which must begin with a letter and contains letters, digits, or hyphens + * [A-Z][A-Z0-9\-]*= + * ( + * The entity value contains any char except for the special chars , \ = < > " # ; or + + * ([A-Za-z0-9 !\$%&'()\*\-\.\/\:\?@\[\]\^_`{\|}~]) + * | + * Or an escaped special char , \ = < > " # ; + or a space, or an 8 bit ASCII code in hex + * (\\([ ,\\\r=<>""#;+]|[0-9A-Fa-f]{2})) + * | + * Or a # followed by a sequence of 8 bit ASCII codes in hex + * (#([0-9A-Fa-f]{2})+) + * There will be 1 or more characters matching this pattern + * )+ + * ) + * Followed by a delimeter, either , or + + * [\+,]\s* + * There may be 0 or more entities matching this pattern + * )* + * + * The second part or the pattern must match an OU entity + * This is identical to the first part, except it must only match the OU entity key + * and does not include a delimiter. If present, a trailing delimiter is matched by the preceding part of the pattern + * ( + * The entity key must match OU exactly. This guarantees the overall pattern contains at least oen OU element + * OU= + * ( + * The entity value contains any char except for the special chars , \ = < > " # ; or + + * ([A-Za-z0-9 !\$%&'()\*\-\.\/\:\?@\[\]\^_`{\|}~]) + * | + * Or an escaped special char , \ = < > " # ; + or a space, or an 8 bit ASCII code in hex + * (\\([ ,\\\r=<>""#;+]|[0-9A-Fa-f]{2})) + * | + * Or a # followed by a sequence of 8 bit ASCII codes in hex + * (#([0-9A-Fa-f]{2})+) + * There will be 1 or more characters matching this pattern + * )+ + * ) + * + * The final component of the pattern matches 0 or more additional elements, almost identical to the + * first component of the pattern, except the delimeters are now leading + * ( + * This compoennt of the pattern begins with a , or + delimiter + * [\+,]\s* + * ( + * The entity begins with a key which must begin with a letter and contains letters, digits, or hyphens + * [A-Z][A-Z0-9\-]*= + * ( + * The entity value contains any char except for the special chars , \ = < > " # ; or + + * ([A-Za-z0-9 !\$%&'()\*\-\.\/\:\?@\[\]\^_`{\|}~]) + * | + * Or an escaped special char , \ = < > " # ; + or a space, or an 8 bit ASCII code in hex + * (\\([ ,\\\r=<>""#;+]|[0-9A-Fa-f]{2})) + * | + * Or a # followed by a sequence of 8 bit ASCII codes in hex + * (#([0-9A-Fa-f]{2})+) + * There will be 1 or more characters matching this pattern + * )+ + * ) + * There may be 0 or more entities matching this pattern, and then the end of the string + * )*$ + * + */ + + protected const string FullYearPattern = @"^(19|20)\d\d$"; + + protected const string TwoDigitMonthPattern = @"^(0[1-9]|1[0-2])$"; + } +} \ No newline at end of file diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/MSSharedLibKey.snk b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/MSSharedLibKey.snk new file mode 100644 index 000000000000..695f1b38774e Binary files /dev/null and b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/MSSharedLibKey.snk differ diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/OperationalResult/GetAzureRemoteAppOperationResult.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/OperationalResult/GetAzureRemoteAppOperationResult.cs new file mode 100644 index 000000000000..93fce080584e --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/OperationalResult/GetAzureRemoteAppOperationResult.cs @@ -0,0 +1,44 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppOperationResult"), OutputType(typeof(OperationResult))] + public class GetAzureRemoteAppOperationResult : RdsCmdlet + { + [Parameter(Mandatory = true, + Position = 0, + HelpMessage = "Operation Identifier")] + + public string TrackingId { get; set; } + + public override void ExecuteCmdlet() + { + RemoteAppOperationStatusResult response = null; + + response = CallClient(() => Client.OperationResults.Get(TrackingId), Client.OperationResults); + + if (response != null) + { + WriteObject(response.RemoteAppOperationResult); + } + + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/RemoteProgram/GetAzureRemoteAppProgram.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/RemoteProgram/GetAzureRemoteAppProgram.cs new file mode 100644 index 000000000000..6cc7eed20bf3 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/RemoteProgram/GetAzureRemoteAppProgram.cs @@ -0,0 +1,127 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppProgram", DefaultParameterSetName = FilterByName), OutputType(typeof(PublishedApplicationDetails))] + public class GetAzureRemoteAppProgram : RdsCmdlet + { + private const string FilterByName = "FilterByName"; + private const string FilterByAlias = "FilterByAlias"; + + [Parameter(Mandatory = true, + Position = 0, + HelpMessage = "RemoteApp collection name")] + [ValidatePattern(NameValidatorString)] + public string CollectionName { get; set; } + + [Parameter(Mandatory = false, + Position = 1, + HelpMessage = "Name of the program. Wildcards are permitted.", + ParameterSetName = FilterByName)] + [ValidateNotNullOrEmpty()] + public string RemoteAppProgram { get; set; } + + [Parameter(Mandatory = false, + Position = 1, + HelpMessage = "Published program alias", + ParameterSetName = FilterByAlias)] + [ValidateNotNullOrEmpty()] + public string Alias { get; set; } + + private bool found = false; + + private bool GetAllPublishedApps() + { + GetPublishedApplicationListResult response = null; + IEnumerable spList = null; + + response = CallClient(() => Client.Publishing.List(CollectionName), Client.Publishing); + + if (response != null) + { + if (UseWildcard) + { + spList = response.ResultList.Where(app => Wildcard.IsMatch(app.Name)); + } + else + { + spList = response.ResultList; + } + + if (spList != null && spList.Count() > 0) + { + WriteObject(spList, true); + found = true; + } + } + + return found; + } + + private bool GetPublishedApp() + { + GetPublishedApplicationResult response = null; + + response = CallClient(() => Client.Publishing.Get(CollectionName, Alias), Client.Publishing); + + if (response != null) + { + WriteObject(response.Result); + found = true; + } + + return found; + } + + public override void ExecuteCmdlet() + { + if (!String.IsNullOrWhiteSpace(Alias)) + { + found = GetPublishedApp(); + if (!found) + { + WriteErrorWithTimestamp( + String.Format("Collection {0} does not have a published program matching alias {1}.", + CollectionName, + Alias) + ); + } + } + else + { + CreateWildcardPattern(RemoteAppProgram); + + found = GetAllPublishedApps(); + + if (!found) + { + WriteVerboseWithTimestamp( + String.Format("Collection {0} has no published program matching: {1}.", + CollectionName, + RemoteAppProgram) + ); + } + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/RemoteProgram/GetStartMenuProgram.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/RemoteProgram/GetStartMenuProgram.cs new file mode 100644 index 000000000000..cfafa22adb8a --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/RemoteProgram/GetStartMenuProgram.cs @@ -0,0 +1,130 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppStartMenuProgram"), OutputType(typeof(StartMenuApplication))] + public class GetStartMenuProgram : RdsCmdlet + { + [Parameter (Mandatory = true, + Position = 0, + HelpMessage = "RemoteApp collection name")] + [ValidatePattern (NameValidatorString)] + public string CollectionName { get; set; } + + [Parameter(Mandatory = false, + Position = 1, + HelpMessage = "Unique alias of the program, Wildcards are permitted.")] + [ValidateNotNullOrEmpty()] + public string ProgramName { get; set; } + + public class ApplicationComparer : IComparer + { + public int Compare(StartMenuApplication first, StartMenuApplication second) + { + if (first == null) + { + if (second == null) + { + return 0; // both null are equal + } + else + { + return -1; // second is greateer + } + } + else + { + if (second == null) + { + return 1; // first is greater as it is not null + } + } + + return string.Compare(first.Name, second.Name, StringComparison.OrdinalIgnoreCase); + } + } + + public override void ExecuteCmdlet() + { + GetStartMenuApplicationListResult result = null; + bool getAllPrograms = false; + bool found = false; + + if (String.IsNullOrWhiteSpace(ProgramName)) + { + getAllPrograms = true; + } + else + { + CreateWildcardPattern(ProgramName); + } + + result = CallClient(() => Client.Publishing.StartMenuApplicationList(CollectionName), Client.Publishing); + if (result != null && result.ResultList != null) + { + if (ExactMatch) + { + StartMenuApplication application = null; + application = result.ResultList.FirstOrDefault(app => String.Equals(app.Name, ProgramName, StringComparison.InvariantCultureIgnoreCase)); + + if (application == null) + { + WriteErrorWithTimestamp("Program: " + ProgramName + " does not exist in collection " + CollectionName); + found = false; + } + else + { + WriteObject(application); + found = true; + } + } + else + { + IEnumerable matchingApps = null; + if (getAllPrograms) + { + matchingApps = result.ResultList; + } + else if (UseWildcard) + { + matchingApps = result.ResultList.Where(app => Wildcard.IsMatch(app.Name)); + } + + if (matchingApps != null && matchingApps.Count() > 0) + { + List applications = new List(matchingApps); + IComparer comparer = new ApplicationComparer(); + applications.Sort(comparer); + WriteObject(applications, true); + found = true; + } + } + } + + if (!found && !getAllPrograms) + { + WriteVerboseWithTimestamp(String.Format("Program '{0}' was not found in Collection '{1}'.", ProgramName, CollectionName)); + } + } + } +} \ No newline at end of file diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/RemoteProgram/PublishAzureRemoteAppProgram.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/RemoteProgram/PublishAzureRemoteAppProgram.cs new file mode 100644 index 000000000000..e16b0f566b61 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/RemoteProgram/PublishAzureRemoteAppProgram.cs @@ -0,0 +1,149 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.IO; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsData.Publish, "AzureRemoteAppProgram", DefaultParameterSetName = AppId), OutputType(typeof(PublishingOperationResult))] + public class PublishAzureRemoteAppProgram : RdsCmdlet + { + private const string AppPath = "App Path"; + private const string AppId = "App Id"; + + [Parameter (Mandatory = true, + Position = 0, + HelpMessage = "RemoteApp collection name")] + [ValidatePattern (NameValidatorString)] + public string CollectionName { get; set; } + + [Parameter(Mandatory = true, + Position = 1, + ParameterSetName = AppPath, + HelpMessage = "Virtual file path of the program to be published.")] + public string FileVirtualPath { get; set; } + + [Parameter(Mandatory = true, + Position = 1, + ValueFromPipelineByPropertyName = true, + ParameterSetName = AppId, + HelpMessage = "Start menu program ID of the program to be published.")] + public string StartMenuAppId { get; set; } + + [Parameter(Mandatory = false, + HelpMessage = "Command-line argument for the program to be published.")] + [ValidateNotNullOrEmpty()] + public string CommandLine { get; set; } + + [Parameter(Mandatory = false, + HelpMessage = "Display name of the program to be published.")] + [ValidateNotNullOrEmpty()] + public string DisplayName { get; set; } + + [Parameter(Mandatory = false, + HelpMessage = "Allows to run the cmdlet in the background as a PS job.")] + SwitchParameter AsJob { get; set; } + + private LongRunningTask task = null; + + private ApplicationDetailsListParameter VerifyPreconditions() + { + ApplicationDetailsListParameter appDetails = new ApplicationDetailsListParameter() + { + DetailsList = new System.Collections.Generic.List() + { + new PublishedApplicationDetails() + } + }; + + string appName = null; + string appPath = null; + string iconURI = null; + IconPngUrisType iconPngUris = new IconPngUrisType(); + + switch (ParameterSetName) + { + case AppPath: + { + appName = Path.GetFileNameWithoutExtension(FileVirtualPath); + appPath = FileVirtualPath; + + break; + } + case AppId: + { + GetStartMenuApplicationResult startMenu = Client.Publishing.StartMenuApplication(CollectionName, StartMenuAppId); + appName = startMenu.Result.Name; + appPath = startMenu.Result.VirtualPath; + iconURI = startMenu.Result.IconUri; + iconPngUris = new IconPngUrisType() + { + IconPngUris = startMenu.Result.IconPngUris, + }; + break; + } + } + + appDetails.DetailsList[0].Name = String.IsNullOrWhiteSpace(DisplayName) ? appName : DisplayName; + appDetails.DetailsList[0].VirtualPath = appPath; + + appDetails.DetailsList[0].IconUri = iconURI; + appDetails.DetailsList[0].IconPngUris = iconPngUris; + + appDetails.DetailsList[0].Alias = ""; + + appDetails.DetailsList[0].CommandLineArguments = CommandLine; + + appDetails.DetailsList[0].AvailableToUsers = true; + + return appDetails; + } + + private void StartApplicationPublishing(ApplicationDetailsListParameter appDetails) + { + PublishApplicationsResult response = CallClient(() => Client.Publishing.PublishApplications(CollectionName, appDetails), Client.Publishing); + + WriteObject(response.ResultList, true); + } + + public override void ExecuteCmdlet() + { + if (AsJob.IsPresent) + { + task = new LongRunningTask(this, "RemoteAppBackgroundTask", "Publish RemoteApp"); + + task.ProcessJob(() => + { + PublishAction(); + task.SetStatus("ProcessJob completed"); + }); + } + else + { + PublishAction(); + } + } + + private void PublishAction() + { + ApplicationDetailsListParameter appDetails = VerifyPreconditions(); + StartApplicationPublishing(appDetails); + } + + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/RemoteProgram/UnpublishAzureRemoteAppProgram.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/RemoteProgram/UnpublishAzureRemoteAppProgram.cs new file mode 100644 index 000000000000..8ba731668933 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/RemoteProgram/UnpublishAzureRemoteAppProgram.cs @@ -0,0 +1,68 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Management.Automation; +using System.Collections.Generic; +using Microsoft.Azure.Commands.RemoteApp; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsData.Unpublish, "AzureRemoteAppProgram", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High), OutputType(typeof(PublishingOperationResult))] + public class UnpublishAzureRemoteAppProgram : RdsCmdlet + { + [Parameter (Mandatory = true, + Position = 0, + HelpMessage = "RemoteApp collection name")] + [ValidatePattern (NameValidatorString)] + public string CollectionName { get; set; } + + [Parameter(Mandatory = false, + Position = 1, + HelpMessage = "Aliases of the programs to unpublish")] + [ValidateNotNullOrEmpty()] + public string[] Alias { get; set; } + + public override void ExecuteCmdlet() + { + UnpublishApplicationsResult result = null; + + AliasesListParameter appAlias = new AliasesListParameter() + { + AliasesList = new List(Alias) + }; + + if (appAlias.AliasesList.Count == 0) + { + if (ShouldProcess(Commands_RemoteApp.UnpublishProgramConfirmationDescription, + Commands_RemoteApp.GenericAreYouSureQuestion, + Commands_RemoteApp.UnpublishProgramCaptionMessage)) + { + result = CallClient(() => Client.Publishing.UnpublishAll(CollectionName), Client.Publishing); + } + } + else + { + appAlias.AliasesList = new List(Alias); + result = CallClient(() => Client.Publishing.Unpublish(CollectionName, appAlias), Client.Publishing); + } + + if (result != null && result.ResultList != null) + { + WriteObject(result.ResultList, true); + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/AddAzureRemoteAppUser.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/AddAzureRemoteAppUser.cs new file mode 100644 index 000000000000..aed431b0e3e0 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/AddAzureRemoteAppUser.cs @@ -0,0 +1,29 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Add, "AzureRemoteAppUser"), OutputType(typeof(SecurityPrincipalOperationsResult))] + public class AddAzureRemoteAppUser : SecurityPrincipals + { + public override void ExecuteCmdlet() + { + AddUsers(CollectionName, UserUpn, Type); + } + } +} + diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/GetAzureRemoteAppUser.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/GetAzureRemoteAppUser.cs new file mode 100644 index 000000000000..d7c7ea0dc90b --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/GetAzureRemoteAppUser.cs @@ -0,0 +1,140 @@ +// ---------------------------------------------------------------------------------- +// +// 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 LocalModels; +using Microsoft.Azure.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppUser"), OutputType(typeof(ConsentStatusModel))] + public class GetAzureRemoteAppUser : RdsCmdlet + { + [Parameter (Mandatory = true, + Position = 0, + HelpMessage = "RemoteApp collection name")] + [ValidatePattern (NameValidatorString)] + public string CollectionName { get; set; } + + [Parameter(Mandatory = false, + Position = 1, + HelpMessage = "User name. Wildcard pattern supported.")] + [ValidateNotNullOrEmpty()] + public string UserUpn { get; set; } + + public class ServicePrincipalComparer : IComparer + { + public int Compare(SecurityPrincipalInfo first, SecurityPrincipalInfo second) + { + if (first == null) + { + if (second == null) + { + return 0; // both null are equal + } + else + { + return -1; // second is greateer + } + } + else + { + if (second == null) + { + return 1; // first is greater as it is not null + } + } + + return string.Compare(first.SecurityPrincipal.Name, second.SecurityPrincipal.Name, StringComparison.OrdinalIgnoreCase); + } + } + + public override void ExecuteCmdlet() + { + SecurityPrincipalInfoListResult response = null; + ConsentStatusModel model = null; + bool showAllUsers = String.IsNullOrWhiteSpace(UserUpn); + bool found = false; + + if (showAllUsers == false) + { + CreateWildcardPattern(UserUpn); + } + + response = CallClient(() => Client.Principals.List(CollectionName), Client.Principals); + + if (response != null && response.SecurityPrincipalInfoList != null) + { + if (ExactMatch) + { + SecurityPrincipalInfo userconsent = null; + + userconsent = response.SecurityPrincipalInfoList.FirstOrDefault(user => user.SecurityPrincipal.SecurityPrincipalType == PrincipalType.User && + String.Equals(user.SecurityPrincipal.Name, UserUpn, StringComparison.OrdinalIgnoreCase)); + + if (userconsent == null) + { + WriteErrorWithTimestamp("User: " + UserUpn + " does not exist in collection " + CollectionName); + found = false; + } + else + { + model = new ConsentStatusModel(userconsent); + WriteObject(model); + found = true; + } + } + else + { + IEnumerable spList = null; + + if (showAllUsers) + { + spList = response.SecurityPrincipalInfoList.Where(user => user.SecurityPrincipal.SecurityPrincipalType == PrincipalType.User); + } + else + { + spList = response.SecurityPrincipalInfoList.Where(user => user.SecurityPrincipal.SecurityPrincipalType == PrincipalType.User && + Wildcard.IsMatch(user.SecurityPrincipal.Name)); + } + + if (spList != null && spList.Count() > 0) + { + List userConsents = new List(spList); + IComparer comparer = new ServicePrincipalComparer(); + + userConsents.Sort(comparer); + foreach (SecurityPrincipalInfo consent in spList) + { + model = new ConsentStatusModel(consent); + WriteObject(model); + } + found = true; + } + } + } + + if (!found && !showAllUsers) + { + WriteVerboseWithTimestamp(String.Format("User '{0}' is not assigned to Collection '{1}'.", UserUpn, CollectionName)); + } + + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/Model/ConsentStatusModel.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/Model/ConsentStatusModel.cs new file mode 100644 index 000000000000..076ce9214b84 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/Model/ConsentStatusModel.cs @@ -0,0 +1,34 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp.Models; + +namespace LocalModels +{ + public class ConsentStatusModel + { + public ConsentStatusModel(SecurityPrincipalInfo securityPrincipalInfo) + { + Name = securityPrincipalInfo.SecurityPrincipal.Name; + UserIdType = securityPrincipalInfo.SecurityPrincipal.UserIdType; + ConsentStatus = securityPrincipalInfo.Status; + } + public string Name { get; set; } + + public PrincipalProviderType UserIdType { get; set; } + + public ConsentStatus ConsentStatus { get; set; } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/RemoveAzureRemoteAppUser.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/RemoveAzureRemoteAppUser.cs new file mode 100644 index 000000000000..c729d21a0dbd --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/RemoveAzureRemoteAppUser.cs @@ -0,0 +1,28 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Remove, "AzureRemoteAppUser"), OutputType(typeof(SecurityPrincipalOperationsResult))] + public class RemoveAzureRemoteAppUser : SecurityPrincipals + { + public override void ExecuteCmdlet() + { + RemoveUsers(CollectionName, UserUpn, Type); + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/SecurityPrincipals.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/SecurityPrincipals.cs new file mode 100644 index 000000000000..2d979e110987 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/SecurityPrincipals/SecurityPrincipals.cs @@ -0,0 +1,175 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.Collections.Generic; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + public class SecurityPrincipals : RdsCmdlet + { + [Parameter(Mandatory = true, + Position = 0, + HelpMessage = "RemoteApp collection name")] + [ValidatePattern(NameValidatorString)] + public string CollectionName { get; set; } + + [Parameter(Mandatory = true, + Position = 1, + HelpMessage = "The user type" + )] + public PrincipalProviderType Type { get; set; } + + [Parameter(Mandatory = true, + Position = 2, + ValueFromPipeline = false, + HelpMessage = "One or more user UPNs to add to the RemoteApp collection.")] + [ValidatePattern(UserPrincipalValdatorString)] + public string[] UserUpn { get; set; } + + protected enum Operation + { + Add, + Remove + } + + protected void AddUsers(string CollectionName, string[] users, PrincipalProviderType userIdType) + { + SecurityPrincipalOperationsResult response = null; + SecurityPrincipalList spAdd = null; + + if (!String.IsNullOrWhiteSpace(CollectionName)) + { + spAdd = BuildUserList(users, userIdType); + + response = CallClient(() => Client.Principals.Add(CollectionName, spAdd), Client.Principals); + } + + if (response != null) + { + ProcessResult(response, CollectionName, Operation.Add); + } + } + + protected void RemoveUsers(string CollectionName, string[] users, PrincipalProviderType userIdType) + { + SecurityPrincipalOperationsResult response = null; + SecurityPrincipalList spRemove = null; + + if (!String.IsNullOrWhiteSpace(CollectionName)) + { + spRemove = BuildUserList(users, userIdType); + + response = CallClient(() => Client.Principals.Delete(CollectionName, spRemove), Client.Principals); + } + + if (response != null) + { + ProcessResult(response, CollectionName, Operation.Remove); + } + } + + protected SecurityPrincipalList BuildUserList(string[] Users, PrincipalProviderType userIdType) + { + SecurityPrincipalList userList = new SecurityPrincipalList(); + List spList = new List(); + + foreach (string user in Users) + { + SecurityPrincipal principal = new SecurityPrincipal() + { + AadObjectId = null, + Description = null, + Name = user, + SecurityPrincipalType = PrincipalType.User, + UserIdType = userIdType + }; + + spList.Add(principal); + } + + userList.SecurityPrincipals = spList; + + return userList; + } + + protected void ProcessResult( SecurityPrincipalOperationsResult result, string collectionName, Operation operation) + { + ErrorRecord er = null; + ErrorCategory category = ErrorCategory.NotImplemented; + String errorMessageFormat = String.Empty; + + if (result.Errors != null) + { + switch (operation) + { + case Operation.Add: + errorMessageFormat = "Could not add {0} to collection {1} because of error: {2} [{3}]."; + break; + case Operation.Remove: + errorMessageFormat = "Could not remove {0} from collection {1} because of error: {2} [{3}]."; + break; + default: + errorMessageFormat = "Unknown error."; + break; + } + + foreach (SecurityPrincipalOperationErrorDetails errorDetails in result.Errors) + { + + switch (errorDetails.Error) + { + case SecurityPrincipalOperationError.NotSupported: + case SecurityPrincipalOperationError.AlreadyExists: + case SecurityPrincipalOperationError.AssignedToAnotherCollection: + { + category = ErrorCategory.InvalidOperation; + break; + } + + case SecurityPrincipalOperationError.NotFound: + case SecurityPrincipalOperationError.CouldNotBeResolved: + case SecurityPrincipalOperationError.NotDirsynced: + { + category = ErrorCategory.ObjectNotFound; + break; + } + } + + er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + String.Format(errorMessageFormat, + errorDetails.SecurityPrincipal, + collectionName, + errorDetails.Error.ToString(), + errorDetails.ErrorDetails + ), + String.Empty, + Client.Principals, + category + ); + + WriteError(er); + } + } + else + { + WriteObject(result); + } + } + } + +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Sessions/DisconnectAzureRemoteAppSession.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Sessions/DisconnectAzureRemoteAppSession.cs new file mode 100644 index 000000000000..00e26620565b --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Sessions/DisconnectAzureRemoteAppSession.cs @@ -0,0 +1,52 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + [Cmdlet(VerbsCommunications.Disconnect, "AzureRemoteAppSession"), OutputType(typeof(string))] + public class DisconnectAzureRemoteAppSession : RdsCmdlet + { + [Parameter(Mandatory = true, + Position = 0, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true, + HelpMessage = "RemoteApp collection name")] + [ValidatePattern(NameValidatorString)] + public string CollectionName { get; set; } + + [Parameter(Mandatory = true, + ValueFromPipeline = false, + Position = 1, + HelpMessage = "User UPN")] + [ValidatePattern(UserPrincipalValdatorString)] + public string UserUpn { get; set; } + + public override void ExecuteCmdlet() + { + SessionCommandParameter parameter = new SessionCommandParameter + { + UserUpn = UserUpn + }; + + var response = CallClient(() => Client.Collections.DisconnectSession(CollectionName, parameter), Client.Collections); + + WriteTrackingId(response); + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Sessions/GetAzureRemoteAppSession.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Sessions/GetAzureRemoteAppSession.cs new file mode 100644 index 000000000000..f669aafd6b4f --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Sessions/GetAzureRemoteAppSession.cs @@ -0,0 +1,97 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Collections.Generic; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppSession"), OutputType(typeof(RemoteAppSession))] + public class GetAzureRemoteAppSession : RdsCmdlet + { + [Parameter(Mandatory = true, + Position = 0, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true, + HelpMessage = "RemoteApp collection name")] + [ValidatePattern(NameValidatorString)] + public string CollectionName { get; set; } + + [Parameter(Mandatory = false, + Position = 1, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Session user UPN. Wildcards are permitted.")] + public string UserUpn { get; set; } + + public override void ExecuteCmdlet() + { + CollectionSessionListResult response = null; + List sessions = new List(); + + if (!string.IsNullOrWhiteSpace(UserUpn)) + { + CreateWildcardPattern(UserUpn); + } + + response = CallClient(() => Client.Collections.ListSessions(CollectionName), Client.Collections); + + if (ExactMatch) + { + foreach (RemoteAppSession session in response.Sessions) + { + if (string.Equals(session.UserUpn, UserUpn)) + { + sessions.Add(session); + break; + } + } + + if (sessions.Count == 0) + { + WriteErrorWithTimestamp("No session found matching " + UserUpn); + } + } + else + { + if (UseWildcard) + { + foreach (RemoteAppSession session in response.Sessions) + { + if (Wildcard.IsMatch(session.UserUpn)) + { + sessions.Add(session); + } + } + } + else + { + sessions.AddRange(response.Sessions); + } + + if (response.Sessions.Count == 0) + { + WriteVerboseWithTimestamp("No sessions found in collection."); + } + else + { + WriteObject(response.Sessions, true); + } + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Sessions/InvokeAzureRemoteAppSessionLogoff.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Sessions/InvokeAzureRemoteAppSessionLogoff.cs new file mode 100644 index 000000000000..83b190c5e6f1 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Sessions/InvokeAzureRemoteAppSessionLogoff.cs @@ -0,0 +1,66 @@ +// ---------------------------------------------------------------------------------- +// +// 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.RemoteApp; +using Microsoft.Azure.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsLifecycle.Invoke, "AzureRemoteAppSessionLogoff", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High), OutputType(typeof(string))] + public class InvokeAzureRemoteAppSessionLogoff : RdsCmdlet + { + [Parameter(Mandatory = true, + Position = 0, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true, + HelpMessage = "RemoteApp collection name")] + [ValidatePattern(NameValidatorString)] + public string CollectionName { get; set; } + + [Parameter(Mandatory = true, + ValueFromPipeline = false, + Position = 1, + HelpMessage = "User UPN")] + [ValidatePattern(UserPrincipalValdatorString)] + public string UserUpn { get; set; } + + public override void ExecuteCmdlet() + { + SessionCommandParameter parameter = new SessionCommandParameter + { + UserUpn = UserUpn + }; + + OperationResultWithTrackingId response = null; + string caption = Commands_RemoteApp.SessionLogOffCaptionMessage; + string warning = String.Format(System.Globalization.CultureInfo.CurrentCulture, + Commands_RemoteApp.SessionLogOffWarningQuestionFormat, + UserUpn); + + if (ShouldProcess(caption, warning, caption)) + { + + response = CallClient(() => Client.Collections.LogoffSession(CollectionName, parameter), Client.Collections); + } + + if (response != null) + { + WriteTrackingId(response); + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Sessions/SendAzureRemoteAppSessionMessage.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Sessions/SendAzureRemoteAppSessionMessage.cs new file mode 100644 index 000000000000..8568ebd574b9 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Sessions/SendAzureRemoteAppSessionMessage.cs @@ -0,0 +1,59 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + [Cmdlet(VerbsCommunications.Send, "AzureRemoteAppSessionMessage"), OutputType(typeof(string))] + public class SendAzureRemoteAppSessionMessage : RdsCmdlet + { + [Parameter(Mandatory = true, + Position = 0, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true, + HelpMessage = "RemoteApp collection name")] + [ValidatePattern(NameValidatorString)] + public string CollectionName { get; set; } + + [Parameter(Mandatory = true, + ValueFromPipeline = false, + Position = 1, + HelpMessage = "User UPN")] + [ValidatePattern(UserPrincipalValdatorString)] + public string UserUpn { get; set; } + + [Parameter(Mandatory = true, + ValueFromPipeline = false, + Position = 2, + HelpMessage = "Message")] + public string Message { get; set; } + + public override void ExecuteCmdlet() + { + SessionSendMessageCommandParameter parameter = new SessionSendMessageCommandParameter + { + UserUpn = UserUpn, + Message = Message + }; + + var response = CallClient(() => Client.Collections.SendMessageToSession(CollectionName, parameter), Client.Collections); + + WriteTrackingId(response); + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/GetAzureRemoteAppTemplateImage.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/GetAzureRemoteAppTemplateImage.cs new file mode 100644 index 000000000000..6737c0cc868a --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/GetAzureRemoteAppTemplateImage.cs @@ -0,0 +1,127 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.Collections.Generic; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppTemplateImage"), OutputType(typeof(TemplateImage))] + public class GetAzureRemoteAppTemplateImage : GoldImage + { + [Parameter(Mandatory = false, + Position = 0, + ValueFromPipeline = true, + HelpMessage = "Template image name. Wildcards are permitted.")] + [ValidateNotNullOrEmpty()] + public string ImageName { get; set; } + + private bool showAllImages = false; + + private bool found = false; + + public void WriteImage(List templateImages) + { + IComparer comparer = new TemplateImageComparer(); + templateImages.Sort(comparer); + + WriteObject(templateImages, true); + found = true; + } + + private bool GetAllTemplates() + { + TemplateImageListResult response = null; + + response = CallClient(() => Client.TemplateImages.List(), Client.TemplateImages); + + if (response != null) + { + List customerImages = new List(); + List platformImages = new List(); + List unknownImages = new List(); + + foreach (TemplateImage image in response.RemoteAppTemplateImageList) + { + if (UseWildcard && !Wildcard.IsMatch(image.Name)) + { + continue; + } + + switch (image.Type) + { + case TemplateImageType.CustomerImage: + { + customerImages.Add(image); + break; + } + case TemplateImageType.PlatformImage: + { + platformImages.Add(image); + break; + } + default: + { + unknownImages.Add(image); + break; + } + } + } + + WriteImage(unknownImages); + WriteImage(customerImages); + WriteImage(platformImages); + } + return found; + } + + private bool GetTemplate(string imageName) + { + TemplateImageResult response = null; + response = CallClient(() => Client.TemplateImages.Get(imageName), Client.TemplateImages); + if (response != null) + { + WriteObject(response.TemplateImage); + } + return found; + } + + public override void ExecuteCmdlet() + { + showAllImages = String.IsNullOrWhiteSpace(ImageName); + + if (showAllImages == false) + { + CreateWildcardPattern(ImageName); + } + + if (ExactMatch) + { + found = GetTemplate(ImageName); + } + else + { + found = GetAllTemplates(); + } + + if (!found) + { + WriteVerboseWithTimestamp(String.Format("RemoteApp image: {0} not found", ImageName)); + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/NewAzureRemoteAppTemplateImage.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/NewAzureRemoteAppTemplateImage.cs new file mode 100644 index 000000000000..2c41bb9890d6 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/NewAzureRemoteAppTemplateImage.cs @@ -0,0 +1,468 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + using Microsoft.Azure.Management.RemoteApp; + using Microsoft.Azure.Management.RemoteApp.Models; + using Microsoft.WindowsAzure.Management.Compute; + using Microsoft.WindowsAzure.Management.Compute.Models; + using Microsoft.WindowsAzure.Management.Storage; + using Microsoft.WindowsAzure.Management.Storage.Models; + using Microsoft.WindowsAzure.Storage.Auth; + using Microsoft.WindowsAzure.Storage.Blob; + using System; + using System.Collections.ObjectModel; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using System.Threading; + + [Cmdlet(VerbsCommon.New, "AzureRemoteAppTemplateImage", DefaultParameterSetName = UploadLocalVhd), OutputType(typeof(TemplateImageResult))] + + public class NewAzureRemoteAppTemplateImage : GoldImage + { + private const string UploadLocalVhd = "UploadLocalVhd"; + private const string AzureVmUpload = "AzureVmUpload"; + + [Parameter(Mandatory = true, + Position = 0, + ValueFromPipeline = true, + HelpMessage = "Template image name")] + public string ImageName { get; set; } + + [Parameter(Mandatory = true, + Position = 1, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Location in which the template image will be stored")] + public string Location { get; set; } + + [Parameter(Mandatory = true, + Position = 2, + ValueFromPipelineByPropertyName = true, + ParameterSetName = AzureVmUpload, + HelpMessage = "Sysprep-generalized VM image name in Azure")] + public string AzureVmImageName { get; set; } + + [Parameter(Mandatory = true, + Position = 2, + ValueFromPipelineByPropertyName = true, + ParameterSetName = UploadLocalVhd, + HelpMessage = "Local path to the RemoteApp vhd")] + public string Path { get; set; } + + [Parameter(Mandatory = false, + ValueFromPipelineByPropertyName = false, + ParameterSetName = UploadLocalVhd, + HelpMessage = "Resumes disrupted upload of an in-progress image")] + public SwitchParameter Resume { get; set; } + + private LongRunningTask task = null; + + + private void UploadVhd(TemplateImage image) + { + UploadScriptResult response = null; + response = CallClient_ThrowOnError(() => Client.TemplateImages.GetUploadScript()); + + if (response != null && response.Script != null) + { + RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create(); + Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration); + string uploadFilePath = string.Concat(Environment.GetEnvironmentVariable("temp"), "\\uploadScript.ps1"); + Collection results = null; + Pipeline pipeline = runspace.CreatePipeline(); + Command myCommand = new Command(uploadFilePath); + + try + { + File.WriteAllText(uploadFilePath, response.Script); + } + catch (Exception ex) + { + task.SetState(JobState.Failed, new Exception(string.Format("Failed to write file {0}. Error {1}", uploadFilePath, ex.Message))); + return; + } + + myCommand.Parameters.Add(new CommandParameter("sas", image.Sas)); + myCommand.Parameters.Add(new CommandParameter("uri", image.Uri)); + myCommand.Parameters.Add(new CommandParameter("vhdPath", Path)); + + pipeline.Commands.Add(myCommand); + + runspace.Open(); + results = pipeline.Invoke(); + + if (pipeline.HadErrors) + { + if (pipeline.Error.Count > 0) + { + Collection errors = pipeline.Error.Read() as Collection; + + if (errors != null) + { + foreach(ErrorRecord error in errors) + { + task.Error.Add(error); + } + + task.SetState(JobState.Failed, new Exception("Upload script failed")); + } + } + else + { + task.SetState(JobState.Failed, new Exception("Image upload script failed.")); + } + } + + runspace.Close(); + } + } + + + private void EnsureStorageInRegion(string region) + { + OperationResultWithTrackingId responseWithTrackingId = null; + RemoteAppOperationStatusResult operationalResponse = null; + const int waitPeriodMilliseconds = 5 * 1000; + const int maxIterations = 60; + int counter = 0; + responseWithTrackingId = CallClient_ThrowOnError(() => Client.TemplateImages.EnsureStorageInRegion(region)); + + if (responseWithTrackingId.TrackingId != null) + { + task.SetStatus("Waiting for Storage verification to complete"); + do + { + Thread.Sleep(waitPeriodMilliseconds); + counter++; + operationalResponse = CallClient_ThrowOnError(() => Client.OperationResults.Get(responseWithTrackingId.TrackingId)); + } + while ((operationalResponse.RemoteAppOperationResult.Status == RemoteAppOperationStatus.Pending || + operationalResponse.RemoteAppOperationResult.Status == RemoteAppOperationStatus.InProgress) && + counter < maxIterations); + + if (counter >= maxIterations || operationalResponse.RemoteAppOperationResult.Status != RemoteAppOperationStatus.Success) + { + throw new RemoteAppServiceException("Failed to create storage for collection", ErrorCategory.OperationTimeout); + } + } + } + + private TemplateImage VerifyPreconditions() + { + TemplateImage matchingTemplate = null; + Operation op = Operation.Create; + + if (Resume) + { + op = Operation.Resume; + } + + if (ParameterSetName == UploadLocalVhd) + { + VerifySessionIsElevated(); + } + + matchingTemplate = FilterTemplateImage(ImageName, op); + + return matchingTemplate; + } + + private TemplateImage StartTemplateUpload(TemplateImage image) + { + TemplateImageResult response = null; + TemplateImageDetails details = null; + TemplateImage templateImage = null; + + EnsureStorageInRegion(Location); + + if (Resume) + { + templateImage = image; + } + else + { + details = new TemplateImageDetails() + { + Name = ImageName, + Region = Location + }; + + response = CallClient_ThrowOnError(() => Client.TemplateImages.Set(details)); + + templateImage = response.TemplateImage; + if (templateImage == null) + { + throw new RemoteAppServiceException("Unable to find template by this name in that region", ErrorCategory.ObjectNotFound); + } + } + + return templateImage; + } + + private void ImportTemplateImage() + { + TemplateImageResult response = null; + TemplateImageDetails details = null; + + EnsureStorageInRegion(Location); + FilterTemplateImage(ImageName, Operation.Create); + + details = new TemplateImageDetails() + { + Name = ImageName, + Region = Location, + SourceImageSasUri = GetAzureVmSasUri(AzureVmImageName) + }; + + response = CallClient(() => Client.TemplateImages.Set(details), Client.TemplateImages); + + if (response != null) + { + WriteObject(response.TemplateImage); + } + } + + private string GetAzureVmSasUri(string vmImageName) + { + string mediaLinkUri = null; + Uri uri = null; + StorageManagementClient storageClient = null; + string storageAccountName = null; + StorageAccountGetKeysResponse getKeysResponse = null; + ErrorRecord er = null; + + mediaLinkUri = GetImageMediaLinkUri(vmImageName); + uri = new Uri(mediaLinkUri); + storageClient = new StorageManagementClient(this.Client.Credentials, this.Client.BaseUri); + storageAccountName = uri.Authority.Split('.')[0]; + getKeysResponse = storageClient.StorageAccounts.GetKeys(storageAccountName); + + if (getKeysResponse.StatusCode == System.Net.HttpStatusCode.OK) + { + StorageCredentials credentials = new StorageCredentials(storageAccountName, getKeysResponse.SecondaryKey); + SharedAccessBlobPolicy accessPolicy = new SharedAccessBlobPolicy(); + CloudPageBlob pageBlob = null; + string sas = null; + + pageBlob = new CloudPageBlob(uri, credentials); + + accessPolicy.Permissions = SharedAccessBlobPermissions.Read; + accessPolicy.SharedAccessStartTime = DateTime.Now; + accessPolicy.SharedAccessExpiryTime = DateTime.Now.AddHours(12); + + sas = pageBlob.GetSharedAccessSignature(accessPolicy); + + if (sas != null) + { + return mediaLinkUri + sas; + } + else + { + er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + "Couldn't get Sas for template image uri.", + String.Empty, + Client.TemplateImages, + ErrorCategory.ConnectionError + ); + + ThrowTerminatingError(er); + } + } + else + { + er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + String.Format("Couldn't get storage account keys. Error {0}", getKeysResponse.StatusCode.ToString()), + String.Empty, + Client.TemplateImages, + ErrorCategory.ConnectionError + ); + + ThrowTerminatingError(er); + } + + return null; + } + + public string GetImageMediaLinkUri(string vmImageName) + { + ComputeManagementClient computeClient = new ComputeManagementClient(this.Client.Credentials, this.Client.BaseUri); + VirtualMachineOSImageGetResponse imageGetResponse = null; + VirtualMachineVMImageListResponse vmList = null; + string osType = null; + string mediaLinkUri = null; + ErrorRecord er = null; + + try + { + imageGetResponse = computeClient.VirtualMachineOSImages.Get(vmImageName); + + if (imageGetResponse != null) + { + osType = imageGetResponse.OperatingSystemType; + + if (imageGetResponse.MediaLinkUri != null) + { + mediaLinkUri = imageGetResponse.MediaLinkUri.AbsoluteUri; + } + } + } + catch (Hyak.Common.CloudException cloudEx) + { + if (cloudEx.Error.Code == "ResourceNotFound") + { + try + { + vmList = computeClient.VirtualMachineVMImages.List(); + + foreach (VirtualMachineVMImageListResponse.VirtualMachineVMImage image in vmList.VMImages) + { + if (string.Compare(image.Name, vmImageName, true) == 0) + { + if (image.OSDiskConfiguration != null) + { + osType = image.OSDiskConfiguration.OperatingSystem; + + if (image.OSDiskConfiguration.MediaLink != null) + { + mediaLinkUri = image.OSDiskConfiguration.MediaLink.AbsoluteUri; + } + + break; + } + else + { + er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + string.Format("No OSDiskConfiguration found for image {0}.", vmImageName), + String.Empty, + Client.TemplateImages, + ErrorCategory.InvalidArgument + ); + + ThrowTerminatingError(er); + } + } + } + } + catch + { + throw; + } + } + else + { + throw; + } + } + catch (Exception ex) + { + er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + ex.Message, + String.Empty, + Client.TemplateImages, + ErrorCategory.InvalidArgument + ); + + ThrowTerminatingError(er); + } + + if (string.Compare(osType, "Windows", true) != 0) + { + er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + osType == null ? String.Format("Couldn't find image with name {0}", vmImageName) : + String.Format("Invalid Argument: OS Image type is {0}. It must be Windows.", osType), + String.Empty, + Client.TemplateImages, + ErrorCategory.InvalidArgument + ); + + ThrowTerminatingError(er); + } + + if (string.IsNullOrEmpty(mediaLinkUri)) + { + er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + String.Format("Invalid Argument: Cannot use {0} because it is an Azure Gallery image. Only uploaded images can be used.", vmImageName), + String.Empty, + Client.TemplateImages, + ErrorCategory.InvalidArgument + ); + + ThrowTerminatingError(er); + } + + return mediaLinkUri; + } + + public override void ExecuteCmdlet() + { + // register the subscription for this service if it has not been before + // sebsequent call to register is redundent + RegisterSubscriptionWithRdfeForRemoteApp(); + + switch (ParameterSetName) + { + case UploadLocalVhd: + { + string scriptBlock = "Test-Path -Path " + Path; + Collection pathValid = CallPowershellWithReturnType(scriptBlock); + TemplateImage image = null; + + if (pathValid[0] == false) + { + throw new RemoteAppServiceException("Could not validate path to VHD", ErrorCategory.ObjectNotFound); + } + + image = VerifyPreconditions(); + image = StartTemplateUpload(image); + + task = new LongRunningTask(this, "RemoteAppTemplateImageUpload", "Upload RemoteApp Template Image"); + + task.ProcessJob(() => + { + UploadVhd(image); + task.SetStatus("ProcessJob completed"); + }); + + WriteObject(task); + + break; + } + case AzureVmUpload: + { + if (IsFeatureEnabled(EnabledFeatures.goldImageImport)) + { + ImportTemplateImage(); + } + else + { + ErrorRecord er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + string.Format("\"Import Image\" Feature not enabled"), + String.Empty, + Client.Account, + ErrorCategory.InvalidOperation + ); + + ThrowTerminatingError(er); + } + + break; + } + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/RemoveAzureRemoteAppTemplateImage.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/RemoveAzureRemoteAppTemplateImage.cs new file mode 100644 index 000000000000..5b35fb9c8133 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/RemoveAzureRemoteAppTemplateImage.cs @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + [Cmdlet(VerbsCommon.Remove, "AzureRemoteAppTemplateImage", SupportsShouldProcess = true)] + + public class RemoveAzureRemoteAppTemplateImage : GoldImage + { + [Parameter(Mandatory = true, + Position = 0, + ValueFromPipeline = true, + HelpMessage = "Template image name")] + + public string ImageName { get; set; } + + public override void ExecuteCmdlet() + { + AzureOperationResponse response = null; + TemplateImage matchingTemplate = null; + + matchingTemplate = FilterTemplateImage(ImageName, Operation.Remove); + + if (ShouldProcess(ImageName, "Remove image")) + { + response = CallClient(() => Client.TemplateImages.Delete(matchingTemplate.Name), Client.TemplateImages); + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/RenameAzureRemoteAppTemplateImage.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/RenameAzureRemoteAppTemplateImage.cs new file mode 100644 index 000000000000..ab213fe51303 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/RenameAzureRemoteAppTemplateImage.cs @@ -0,0 +1,61 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + [Cmdlet(VerbsCommon.Rename, "AzureRemoteAppTemplateImage"), OutputType(typeof(TemplateImage))] + + public class RenameAzureRemoteAppTemplateImage : GoldImage + { + [Parameter(Mandatory = true, + Position = 0, + ValueFromPipeline = true, + HelpMessage = "Template image name")] + public string ImageName { get; set; } + + [Parameter(Mandatory = true, + Position = 1, + ValueFromPipeline = false, + HelpMessage = "New template image name")] + public string NewName { get; set; } + + public override void ExecuteCmdlet() + { + TemplateImageResult response = null; + TemplateImageDetails details = null; + TemplateImage matchingTemplate = null; + + matchingTemplate = FilterTemplateImage(ImageName, Operation.Update); + + details = new TemplateImageDetails() + { + Id = matchingTemplate.Id, + Region = matchingTemplate.RegionList[0], + Name = NewName + }; + + response = CallClient(() => Client.TemplateImages.Set(details), Client.TemplateImages); + + if (response != null) + { + WriteObject(response.TemplateImage); + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/TemplateImage.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/TemplateImage.cs new file mode 100644 index 000000000000..1408352842e8 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/TemplateImage/TemplateImage.cs @@ -0,0 +1,137 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.Collections.Generic; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + public class TemplateImageComparer : IComparer + { + public int Compare(TemplateImage first, TemplateImage second) + { + if (first == null) + { + if (second == null) + { + return 0; // both null are equal + } + else + { + return -1; // second is greateer + } + } + else + { + if (second == null) + { + return 1; // first is greater as it is not null + } + } + + return string.Compare(first.Name, second.Name, StringComparison.OrdinalIgnoreCase); + } + } + + public class GoldImage : RdsCmdlet + { + public enum Operation + { + Create, + Update, + Remove, + Resume, + } + + protected TemplateImage FilterTemplateImage(string TemplateImageName, Operation op) + { + TemplateImageListResult response = null; + TemplateImage matchingTemplate = null; + string errorMessage = null; + ErrorCategory category = ErrorCategory.NotSpecified; + + response = CallClient_ThrowOnError(() => Client.TemplateImages.List()); + + foreach (TemplateImage template in response.RemoteAppTemplateImageList) + { + if (String.Equals(template.Name, TemplateImageName, StringComparison.OrdinalIgnoreCase)) + { + matchingTemplate = template; + break; + } + } + + switch (op) + { + case Operation.Remove: + case Operation.Update: + { + if (matchingTemplate == null) + { + errorMessage = String.Format("Template {0} does not exist.", TemplateImageName); + category = ErrorCategory.ObjectNotFound; + } + break; + } + case Operation.Create: + { + if (matchingTemplate !=null) + { + errorMessage = String.Format("There is an existing template named {0}.", TemplateImageName); + category = ErrorCategory.ResourceExists; + } + break; + } + case Operation.Resume: + { + if (matchingTemplate == null) + { + errorMessage = String.Format("Template {0} does not exist.", TemplateImageName); + category = ErrorCategory.ObjectNotFound; + } + else if (matchingTemplate.Status != TemplateImageStatus.UploadPending && + matchingTemplate.Status != TemplateImageStatus.UploadInProgress) + { + errorMessage = String.Format( + "Unable to resume uploading this template {0}." + + "It is in the wrong state {1}, it should be either UploadPending or UploadInProgress", + matchingTemplate.Name, + matchingTemplate.Status.ToString()); + category = ErrorCategory.InvalidOperation; + } + else if (DateTime.UtcNow >= matchingTemplate.SasExpiry) + { + errorMessage = String.Format( + "Unable to resume uploading this template {0}. The time limit has expired at {1}", + matchingTemplate.Name, + matchingTemplate.SasExpiry.ToString()); + category = ErrorCategory.InvalidOperation; + } + break; + } + } + + if (errorMessage != null) + { + throw new RemoteAppServiceException(errorMessage, category); + } + + return matchingTemplate; + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/GetAzureRemoteAppVnet.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/GetAzureRemoteAppVnet.cs new file mode 100644 index 000000000000..65f85cacc5ec --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/GetAzureRemoteAppVnet.cs @@ -0,0 +1,125 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppVNet"), OutputType(typeof(VNetResult))] + public class GetAzureRemoteAppVNet : RdsCmdlet + { + [Parameter(Mandatory = false, + Position = 0, + ValueFromPipeline = true, + HelpMessage = "RemoteApp virtual network name. Wildcards are permitted.")] + public string VNetName { get; set; } + + [Parameter(Mandatory = false, + Position = 1, + HelpMessage = "Specify to include the shared VPN key in the output.")] + public SwitchParameter IncludeSharedKey { get; set; } + + private bool GetVNet(string VNet) + { + VNetResult response = null; + bool found = false; + + WriteVerboseWithTimestamp("Getting the VNet."); + + response = CallClient(() => Client.VNet.Get(VNet, IncludeSharedKey), Client.VNet); + if (response != null) + { + WriteObject(response.VNet); + found = true; + } + return found; + } + + private bool GetVNetList(bool showAllVirtualNetworks, string VNet) + { + VNetListResult response = null; + bool found = false; + + if (showAllVirtualNetworks) + { + WriteVerboseWithTimestamp("Getting all VNets."); + } + else if (UseWildcard) + { + WriteVerboseWithTimestamp("Getting the matching VNets for {0}.", VNet); + } + + response = CallClient(() => Client.VNet.List(), Client.VNet); + if (response != null) + { + foreach (VNet vNet in response.VNetList) + { + if (showAllVirtualNetworks || (UseWildcard && Wildcard.IsMatch(vNet.Name))) + { + WriteObject(vNet); + found = true; + } + } + } + return found; + } + + public override void ExecuteCmdlet() + { + bool showAllVirtualNetworks = String.IsNullOrWhiteSpace(VNetName); + bool found = false; + + if (showAllVirtualNetworks == false) + { + CreateWildcardPattern(VNetName); + } + + if (ExactMatch) + { + found = GetVNet(VNetName); + if (!found) + { + WriteErrorWithTimestamp("No VNet found matching: " + VNetName); + } + } + else + { + if (IncludeSharedKey) + { + ErrorRecord er = RemoteAppCollectionErrorState.CreateErrorRecordFromString( + "You must specify an existing unique virtual network name in order to get the shared VPN key.", + String.Empty, + Client.VNet, + ErrorCategory.InvalidOperation + ); + WriteError(er); + } + else + { + found = GetVNetList(showAllVirtualNetworks, VNetName); + } + } + + if (!found) + { + WriteVerboseWithTimestamp(String.Format("Virtual network name: {0} not found.", VNetName)); + } + } + } +} + + diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/GetAzureRemoteAppVpnDevice.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/GetAzureRemoteAppVpnDevice.cs new file mode 100644 index 000000000000..84f252f9b8a9 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/GetAzureRemoteAppVpnDevice.cs @@ -0,0 +1,38 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppVpnDevice"), OutputType(typeof(Vendor))] + public class GetAzureRemoteAppVpnDevice : RdsCmdlet + { + [Parameter(Mandatory = true, + Position = 0, + ValueFromPipeline = true, + HelpMessage = "RemoteApp virtual network name.")] + [ValidatePattern(VNetNameValidatorStringWithWildCards)] + public string VNetName { get; set; } + + public override void ExecuteCmdlet() + { + VNetVpnDeviceResult response = CallClient(() => Client.VNet.GetVpnDevices(VNetName), Client.VNet); + WriteObject(response.Vendors, true); + } + + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/GetAzureRemoteAppVpnDeviceConfigScript.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/GetAzureRemoteAppVpnDeviceConfigScript.cs new file mode 100644 index 000000000000..427527e53708 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/GetAzureRemoteAppVpnDeviceConfigScript.cs @@ -0,0 +1,60 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppVpnDeviceConfigScript"), OutputType(typeof(String))] + public class GetAzureRemoteAppVpnDeviceConfigScript : RdsCmdlet + { + [Parameter(Mandatory = true, + Position = 0, + ValueFromPipeline = true, + HelpMessage = "RemoteApp virtual network name.")] + [ValidatePattern(VNetNameValidatorStringWithWildCards)] + public string VNetName { get; set; } + + [Parameter(Mandatory = true, + Position = 1, + ValueFromPipeline = true, + HelpMessage = "Vendor of the device.")] + public string Vendor { get; set; } + + [Parameter(Mandatory = true, + Position = 2, + ValueFromPipeline = true, + HelpMessage = "Device platform.")] + public string Platform { get; set; } + + [Parameter(Mandatory = true, + Position = 3, + ValueFromPipeline = true, + HelpMessage = "OS Family.")] + public string OSFamily { get; set; } + + public override void ExecuteCmdlet() + { + VNetConfigScriptResult response = CallClient(() => Client.VNet.GetVpnDeviceConfigScript(VNetName, Vendor, Platform, OSFamily), Client.VNet); + if (response != null) + { + WriteObject(response.ConfigScript); + } + } + + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/NewAzureRemoteAppVnet.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/NewAzureRemoteAppVnet.cs new file mode 100644 index 000000000000..f6b2c9531769 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/NewAzureRemoteAppVnet.cs @@ -0,0 +1,88 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp.Models; +using System.Collections.Generic; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.New, "AzureRemoteAppVNet"), OutputType(typeof(TrackingResult))] + public class NewAzureRemoteAppVNet : RdsCmdlet + { + [Parameter(Mandatory = true, + ValueFromPipeline = true, + HelpMessage = "RemoteApp virtual network name.")] + [ValidatePattern(VNetNameValidatorString)] + public string VNetName { get; set; } + + [Parameter(Mandatory = true, + ValueFromPipeline = true, + HelpMessage = "Virtual network address space. Must be in private IP address range and cannot overlap the Local network address space.")] + [ValidatePattern(IPv4CIDR)] + public string[] VirtualNetworkAddressSpace { get; set; } + + [Parameter(Mandatory = true, + ValueFromPipeline = true, + HelpMessage = "Local network address space. Cannot overlap the virtual network address space.")] + [ValidatePattern(IPv4CIDR)] + public string[] LocalNetworkAddressSpace { get; set; } + + [Parameter(Mandatory = true, + ValueFromPipeline = true, + HelpMessage = "DNS Server IP addresses. These must be IPv4 addresses")] + [ValidatePattern(IPv4ValidatorString)] + public string[] DnsServerIpAddress { get; set; } + + [Parameter(Mandatory = true, + ValueFromPipeline = true, + HelpMessage = "Address of the VPN device. Must be a public-facing address in the private IP address range.)")] + [ValidatePattern(IPv4ValidatorString)] + public string VpnDeviceIpAddress { get; set; } + + [Parameter(Mandatory = true, + ValueFromPipeline = true, + HelpMessage = "Virtual network location.")] + public string Location { get; set; } + + [Parameter(Mandatory = true, + ValueFromPipeline = true, + HelpMessage = "Virtual network gateway type")] + public GatewayType GatewayType { get; set; } + + public override void ExecuteCmdlet() + { + VNetParameter payload = null; + OperationResultWithTrackingId response = null; + + payload = new VNetParameter() + { + Region = Location, + VnetAddressSpaces = new List(VirtualNetworkAddressSpace), + LocalAddressSpaces = new List(LocalNetworkAddressSpace), + DnsServers = new List(DnsServerIpAddress), + VpnAddress = VpnDeviceIpAddress, + GatewayType = GatewayType + }; + + RegisterSubscriptionWithRdfeForRemoteApp(); + + response = CallClient(() => Client.VNet.CreateOrUpdate(VNetName, payload), Client.VNet); + if (response != null) + { + WriteTrackingId(response); + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/RemoveAzureAppVnet.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/RemoveAzureAppVnet.cs new file mode 100644 index 000000000000..9bbe0f56982a --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/RemoveAzureAppVnet.cs @@ -0,0 +1,41 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Remove, "AzureRemoteAppVNet"), OutputType(typeof(TrackingResult))] + public class RemoveAzureRemoteAppVNet : RdsCmdlet + { + + [Parameter(Mandatory = true, + ValueFromPipeline = true, + HelpMessage = "RemoteApp virtual network name")] + [ValidatePattern(VNetNameValidatorString)] + + public string VNetName { get; set; } + + public override void ExecuteCmdlet() + { + OperationResultWithTrackingId response = CallClient(() => Client.VNet.Delete(VNetName), Client.VNet); + if (response != null) + { + WriteTrackingId(response); + } + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/ResetAzureRemoteAppVpnSharedKey.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/ResetAzureRemoteAppVpnSharedKey.cs new file mode 100644 index 000000000000..d65995f8e5a4 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/ResetAzureRemoteAppVpnSharedKey.cs @@ -0,0 +1,82 @@ +// ---------------------------------------------------------------------------------- +// +// 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.RemoteApp; +using Microsoft.Azure.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + + [Cmdlet(VerbsCommon.Reset, "AzureRemoteAppVpnSharedKey", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High), OutputType(typeof(VNet))] + public class ResetAzureRemoteAppVpnSharedKey : RdsCmdlet + { + [Parameter(Mandatory = false, + Position = 0, + ValueFromPipeline = true, + HelpMessage = "RemoteApp virtual network name.")] + [ValidatePattern(VNetNameValidatorStringWithWildCards)] + public string VNetName { get; set; } + + public override void ExecuteCmdlet() + { + OperationResultWithTrackingId response = null; + string description = Commands_RemoteApp.VnetSharedKeyResetConfirmationDescription; + string warning = Commands_RemoteApp.GenericAreYouSureQuestion; + string caption = Commands_RemoteApp.VnetSharedKeyResetCaptionMessage; + + if (ShouldProcess(description, warning, caption)) + { + response = CallClient(() => Client.VNet.ResetVpnSharedKey(VNetName), Client.VNet); + } + + if (response != null) + { + VNetOperationStatusResult operationStatus = null; + int maxRetries = 600; // 5 minutes? + // wait for the reset key operation to succeed to get the new key + do + { + System.Threading.Thread.Sleep(5000); //wait a while before the next check + operationStatus = CallClient(() => Client.VNet.GetResetVpnSharedKeyOperationStatus(response.TrackingId), Client.VNet); + + } + while (operationStatus.Status != VNetOperationStatus.Failed && + operationStatus.Status != VNetOperationStatus.Success && + --maxRetries > 0); + + if (operationStatus.Status == VNetOperationStatus.Success) + { + VNetResult vnet = CallClient(() => Client.VNet.Get(VNetName, true), Client.VNet); + WriteObject(vnet.VNet); + + WriteVerboseWithTimestamp("The request completed successfully."); + } + else + { + if (maxRetries > 0) + { + WriteErrorWithTimestamp("The request failed."); + } + else + { + WriteErrorWithTimestamp("The request took a long time to complete."); + } + } + } + } + + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/SetAzureRemoteAppVnet.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/SetAzureRemoteAppVnet.cs new file mode 100644 index 000000000000..68434c4f2743 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/Vnet/SetAzureRemoteAppVnet.cs @@ -0,0 +1,75 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp.Models; +using System.Collections.Generic; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Set, "AzureRemoteAppVNet"), OutputType(typeof(TrackingResult))] + public class SetAzureRemoteAppVNet : RdsCmdlet + { + [Parameter(Mandatory = true, + ValueFromPipeline = true, + HelpMessage = "RemoteApp virtual network name.")] + [ValidatePattern(VNetNameValidatorString)] + public string VNetName { get; set; } + + [Parameter(Mandatory = false, + ValueFromPipeline = true, + HelpMessage = "Virtual network address space. Must be in private IP address range and cannot overlap the Local network address space.")] + [ValidatePattern(IPv4CIDR)] + public string[] VirtualNetworkAddressSpace { get; set; } + + [Parameter(Mandatory = false, + ValueFromPipeline = true, + HelpMessage = "Local network address space. Cannot overlap the virtual network address space.")] + [ValidatePattern(IPv4CIDR)] + public string[] LocalNetworkAddressSpace { get; set; } + + [Parameter(Mandatory = false, + ValueFromPipeline = true, + HelpMessage = "DNS Server IP addresses. These must be IPv4 addresses")] + [ValidatePattern(IPv4ValidatorString)] + public string[] DnsServerIpAddress { get; set; } + + [Parameter(Mandatory = false, + ValueFromPipeline = true, + HelpMessage = "Address of the VPN device. Must be a public facing address in the private IP address range.)")] + [ValidatePattern(IPv4ValidatorString)] + public string VpnDeviceIpAddress { get; set; } + + public override void ExecuteCmdlet() + { + VNetParameter payload = null; + OperationResultWithTrackingId response = null; + + payload = new VNetParameter() + { + VnetAddressSpaces = new List(VirtualNetworkAddressSpace), + LocalAddressSpaces = new List(LocalNetworkAddressSpace), + DnsServers = new List(DnsServerIpAddress), + VpnAddress = VpnDeviceIpAddress, + }; + + response = CallClient(() => Client.VNet.CreateOrUpdate(VNetName, payload), Client.VNet); + if (response != null) + { + WriteTrackingId(response); + } + + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/WorkSpace/GetAzureRemoteAppWorkspace.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/WorkSpace/GetAzureRemoteAppWorkspace.cs new file mode 100644 index 000000000000..6a0d856bb703 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/WorkSpace/GetAzureRemoteAppWorkspace.cs @@ -0,0 +1,38 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Get, "AzureRemoteAppWorkspace"), OutputType(typeof(Workspace))] + public class GetAzureRemoteAppWorkspace : RdsCmdlet + { + public override void ExecuteCmdlet() + { + GetAccountResult response = null; + + response = CallClient(() => Client.Account.Get(), Client.Account); + + if (response != null) + { + Workspace workspace = new Workspace(response); + WriteObject(workspace); + } + } + + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/WorkSpace/Models/WorkSpace.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/WorkSpace/Models/WorkSpace.cs new file mode 100644 index 000000000000..e0438d77c899 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/WorkSpace/Models/WorkSpace.cs @@ -0,0 +1,30 @@ +// ---------------------------------------------------------------------------------- +// +// 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 System.Collections.Generic; + +namespace Microsoft.Azure.Management.RemoteApp.Models +{ + public class Workspace + { + public string ClientUrl { get; set; } + public string EndUserFeedName { get; set; } + + public Workspace(GetAccountResult accountResult) + { + ClientUrl = accountResult.Details.ClientUrl; + EndUserFeedName = accountResult.Details.EndUserFeedName; + } + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/WorkSpace/SetAzureRemoteAppWorkspace.cs b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/WorkSpace/SetAzureRemoteAppWorkspace.cs new file mode 100644 index 000000000000..4e5076e738d0 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/WorkSpace/SetAzureRemoteAppWorkspace.cs @@ -0,0 +1,50 @@ +// ---------------------------------------------------------------------------------- +// +// 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.Management.RemoteApp; +using Microsoft.Azure.Management.RemoteApp.Models; +using System.Management.Automation; + +namespace Microsoft.Azure.Management.RemoteApp.Cmdlets +{ + [Cmdlet(VerbsCommon.Set, "AzureRemoteAppWorkspace"), OutputType(typeof(TrackingResult))] + public class SetAzureRemoteAppWorkspace : RdsCmdlet + { + [Parameter(Mandatory = true, + Position = 0, + ValueFromPipeline = true, + HelpMessage = "RemoteApp Workspace name.")] + public string WorkspaceName { get; set; } + + public override void ExecuteCmdlet() + { + OperationResultWithTrackingId response = null; + AccountDetailsParameter details = new AccountDetailsParameter() + { + AccountInfo = new AccountDetails() + { + EndUserFeedName = WorkspaceName + } + }; + + response = CallClient(() => Client.Account.Set(details), Client.Account); + + if (response != null) + { + WriteTrackingId(response); + } + } + + } +} diff --git a/src/ServiceManagement/RemoteApp/Commands.RemoteApp/packages.config b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/packages.config new file mode 100644 index 000000000000..cd09eda56171 --- /dev/null +++ b/src/ServiceManagement/RemoteApp/Commands.RemoteApp/packages.config @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ServiceManagement/Services/Commands.Utilities/Azure.psd1 b/src/ServiceManagement/Services/Commands.Utilities/Azure.psd1 index 671a309ab785..c4d2e0470029 100644 --- a/src/ServiceManagement/Services/Commands.Utilities/Azure.psd1 +++ b/src/ServiceManagement/Services/Commands.Utilities/Azure.psd1 @@ -85,6 +85,7 @@ NestedModules = '.\Services\Microsoft.WindowsAzure.Commands.dll', '.\HDInsight\Microsoft.WindowsAzure.Commands.HDInsight.dll', '.\Network\Microsoft.Azure.Commands.Network.dll', '.\StorSimple\Microsoft.WindowsAzure.Commands.StorSimple.dll', + '.\RemoteApp\Microsoft.Azure.Commands.RemoteApp.dll', '.\RecoveryServices\Microsoft.Azure.Commands.RecoveryServices.dll' # Functions to export from this module