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