Managing remote application users

This commit is contained in:
vfedosevich 2014-12-17 05:53:53 -08:00
parent 53aff75c14
commit 1c4b8c1006
17 changed files with 881 additions and 6 deletions

View file

@ -88,6 +88,10 @@ namespace WebsitePanel.EnterpriseServer {
private System.Threading.SendOrPostCallback GetOrganizationRdsUsersCountOperationCompleted; private System.Threading.SendOrPostCallback GetOrganizationRdsUsersCountOperationCompleted;
private System.Threading.SendOrPostCallback GetApplicationUsersOperationCompleted;
private System.Threading.SendOrPostCallback SetApplicationUsersOperationCompleted;
/// <remarks/> /// <remarks/>
public esRemoteDesktopServices() { public esRemoteDesktopServices() {
this.Url = "http://localhost:9002/esRemoteDesktopServices.asmx"; this.Url = "http://localhost:9002/esRemoteDesktopServices.asmx";
@ -180,6 +184,12 @@ namespace WebsitePanel.EnterpriseServer {
/// <remarks/> /// <remarks/>
public event GetOrganizationRdsUsersCountCompletedEventHandler GetOrganizationRdsUsersCountCompleted; public event GetOrganizationRdsUsersCountCompletedEventHandler GetOrganizationRdsUsersCountCompleted;
/// <remarks/>
public event GetApplicationUsersCompletedEventHandler GetApplicationUsersCompleted;
/// <remarks/>
public event SetApplicationUsersCompletedEventHandler SetApplicationUsersCompleted;
/// <remarks/> /// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://smbsaas/websitepanel/enterpriseserver/GetRdsCollection", RequestNamespace="http://smbsaas/websitepanel/enterpriseserver", ResponseNamespace="http://smbsaas/websitepanel/enterpriseserver", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://smbsaas/websitepanel/enterpriseserver/GetRdsCollection", RequestNamespace="http://smbsaas/websitepanel/enterpriseserver", ResponseNamespace="http://smbsaas/websitepanel/enterpriseserver", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public RdsCollection GetRdsCollection(int collectionId) { public RdsCollection GetRdsCollection(int collectionId) {
@ -1503,6 +1513,103 @@ namespace WebsitePanel.EnterpriseServer {
} }
} }
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://smbsaas/websitepanel/enterpriseserver/GetApplicationUsers", RequestNamespace="http://smbsaas/websitepanel/enterpriseserver", ResponseNamespace="http://smbsaas/websitepanel/enterpriseserver", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public string[] GetApplicationUsers(int itemId, int collectionId, RemoteApplication remoteApp) {
object[] results = this.Invoke("GetApplicationUsers", new object[] {
itemId,
collectionId,
remoteApp});
return ((string[])(results[0]));
}
/// <remarks/>
public System.IAsyncResult BeginGetApplicationUsers(int itemId, int collectionId, RemoteApplication remoteApp, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("GetApplicationUsers", new object[] {
itemId,
collectionId,
remoteApp}, callback, asyncState);
}
/// <remarks/>
public string[] EndGetApplicationUsers(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult);
return ((string[])(results[0]));
}
/// <remarks/>
public void GetApplicationUsersAsync(int itemId, int collectionId, RemoteApplication remoteApp) {
this.GetApplicationUsersAsync(itemId, collectionId, remoteApp, null);
}
/// <remarks/>
public void GetApplicationUsersAsync(int itemId, int collectionId, RemoteApplication remoteApp, object userState) {
if ((this.GetApplicationUsersOperationCompleted == null)) {
this.GetApplicationUsersOperationCompleted = new System.Threading.SendOrPostCallback(this.OnGetApplicationUsersOperationCompleted);
}
this.InvokeAsync("GetApplicationUsers", new object[] {
itemId,
collectionId,
remoteApp}, this.GetApplicationUsersOperationCompleted, userState);
}
private void OnGetApplicationUsersOperationCompleted(object arg) {
if ((this.GetApplicationUsersCompleted != null)) {
System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg));
this.GetApplicationUsersCompleted(this, new GetApplicationUsersCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState));
}
}
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://smbsaas/websitepanel/enterpriseserver/SetApplicationUsers", RequestNamespace="http://smbsaas/websitepanel/enterpriseserver", ResponseNamespace="http://smbsaas/websitepanel/enterpriseserver", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public ResultObject SetApplicationUsers(int itemId, int collectionId, RemoteApplication remoteApp, string[] users) {
object[] results = this.Invoke("SetApplicationUsers", new object[] {
itemId,
collectionId,
remoteApp,
users});
return ((ResultObject)(results[0]));
}
/// <remarks/>
public System.IAsyncResult BeginSetApplicationUsers(int itemId, int collectionId, RemoteApplication remoteApp, string[] users, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("SetApplicationUsers", new object[] {
itemId,
collectionId,
remoteApp,
users}, callback, asyncState);
}
/// <remarks/>
public ResultObject EndSetApplicationUsers(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult);
return ((ResultObject)(results[0]));
}
/// <remarks/>
public void SetApplicationUsersAsync(int itemId, int collectionId, RemoteApplication remoteApp, string[] users) {
this.SetApplicationUsersAsync(itemId, collectionId, remoteApp, users, null);
}
/// <remarks/>
public void SetApplicationUsersAsync(int itemId, int collectionId, RemoteApplication remoteApp, string[] users, object userState) {
if ((this.SetApplicationUsersOperationCompleted == null)) {
this.SetApplicationUsersOperationCompleted = new System.Threading.SendOrPostCallback(this.OnSetApplicationUsersOperationCompleted);
}
this.InvokeAsync("SetApplicationUsers", new object[] {
itemId,
collectionId,
remoteApp,
users}, this.SetApplicationUsersOperationCompleted, userState);
}
private void OnSetApplicationUsersOperationCompleted(object arg) {
if ((this.SetApplicationUsersCompleted != null)) {
System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg));
this.SetApplicationUsersCompleted(this, new SetApplicationUsersCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState));
}
}
/// <remarks/> /// <remarks/>
public new void CancelAsync(object userState) { public new void CancelAsync(object userState) {
base.CancelAsync(userState); base.CancelAsync(userState);
@ -2262,4 +2369,56 @@ namespace WebsitePanel.EnterpriseServer {
} }
} }
} }
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
public delegate void GetApplicationUsersCompletedEventHandler(object sender, GetApplicationUsersCompletedEventArgs e);
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
public partial class GetApplicationUsersCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs {
private object[] results;
internal GetApplicationUsersCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) :
base(exception, cancelled, userState) {
this.results = results;
}
/// <remarks/>
public string[] Result {
get {
this.RaiseExceptionIfNecessary();
return ((string[])(this.results[0]));
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
public delegate void SetApplicationUsersCompletedEventHandler(object sender, SetApplicationUsersCompletedEventArgs e);
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
public partial class SetApplicationUsersCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs {
private object[] results;
internal SetApplicationUsersCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) :
base(exception, cancelled, userState) {
this.results = results;
}
/// <remarks/>
public ResultObject Result {
get {
this.RaiseExceptionIfNecessary();
return ((ResultObject)(this.results[0]));
}
}
}
} }

View file

@ -203,6 +203,16 @@ namespace WebsitePanel.EnterpriseServer
return GetOrganizationRdsUsersCountInternal(itemId); return GetOrganizationRdsUsersCountInternal(itemId);
} }
public static List<string> GetApplicationUsers(int itemId, int collectionId, RemoteApplication remoteApp)
{
return GetApplicationUsersInternal(itemId, collectionId, remoteApp);
}
public static ResultObject SetApplicationUsers(int itemId, int collectionId, RemoteApplication remoteApp, List<string> users)
{
return SetApplicationUsersInternal(itemId, collectionId, remoteApp, users);
}
private static RdsCollection GetRdsCollectionInternal(int collectionId) private static RdsCollection GetRdsCollectionInternal(int collectionId)
{ {
var collection = ObjectUtils.FillObjectFromDataReader<RdsCollection>(DataProvider.GetRDSCollectionById(collectionId)); var collection = ObjectUtils.FillObjectFromDataReader<RdsCollection>(DataProvider.GetRDSCollectionById(collectionId));
@ -912,6 +922,61 @@ namespace WebsitePanel.EnterpriseServer
return result; return result;
} }
private static List<string> GetApplicationUsersInternal(int itemId, int collectionId, RemoteApplication remoteApp)
{
var result = new List<string>();
Organization org = OrganizationController.GetOrganization(itemId);
if (org == null)
{
return result;
}
var rds = GetRemoteDesktopServices(GetRemoteDesktopServiceID(org.PackageId));
var collection = GetRdsCollection(collectionId);
result.AddRange(rds.GetApplicationUsers(collection.Name, remoteApp.DisplayName));
return result;
}
private static ResultObject SetApplicationUsersInternal(int itemId, int collectionId, RemoteApplication remoteApp, List<string> users)
{
var result = TaskManager.StartResultTask<ResultObject>("REMOTE_DESKTOP_SERVICES", "SET_REMOTE_APP_USERS");
try
{
Organization org = OrganizationController.GetOrganization(itemId);
if (org == null)
{
result.IsSuccess = false;
result.AddError("", new NullReferenceException("Organization not found"));
return result;
}
var collection = GetRdsCollection(collectionId);
var rds = GetRemoteDesktopServices(GetRemoteDesktopServiceID(org.PackageId));
rds.SetApplicationUsers(collection.Name, remoteApp, users.ToArray());
}
catch (Exception ex)
{
result.AddError("REMOTE_DESKTOP_SERVICES_SET_REMOTE_APP_USERS", ex);
}
finally
{
if (!result.IsSuccess)
{
TaskManager.CompleteResultTask(result);
}
else
{
TaskManager.CompleteResultTask();
}
}
return result;
}
private static ResultObject AddRemoteApplicationToCollectionInternal(int itemId, RdsCollection collection, RemoteApplication remoteApp) private static ResultObject AddRemoteApplicationToCollectionInternal(int itemId, RdsCollection collection, RemoteApplication remoteApp)
{ {
var result = TaskManager.StartResultTask<ResultObject>("REMOTE_DESKTOP_SERVICES", "ADD_REMOTE_APP_TO_COLLECTION"); var result = TaskManager.StartResultTask<ResultObject>("REMOTE_DESKTOP_SERVICES", "ADD_REMOTE_APP_TO_COLLECTION");

View file

@ -236,5 +236,16 @@ namespace WebsitePanel.EnterpriseServer
return RemoteDesktopServicesController.GetOrganizationRdsUsersCount(itemId); return RemoteDesktopServicesController.GetOrganizationRdsUsersCount(itemId);
} }
[WebMethod]
public List<string> GetApplicationUsers(int itemId, int collectionId, RemoteApplication remoteApp)
{
return RemoteDesktopServicesController.GetApplicationUsers(itemId, collectionId, remoteApp);
}
[WebMethod]
public ResultObject SetApplicationUsers(int itemId, int collectionId, RemoteApplication remoteApp, List<string> users)
{
return RemoteDesktopServicesController.SetApplicationUsers(itemId, collectionId, remoteApp, users);
}
} }
} }

View file

@ -61,5 +61,7 @@ namespace WebsitePanel.Providers.RemoteDesktopServices
bool CheckSessionHostFeatureInstallation(string hostName); bool CheckSessionHostFeatureInstallation(string hostName);
bool CheckServerAvailability(string hostName); bool CheckServerAvailability(string hostName);
string[] GetApplicationUsers(string collectionName, string applicationName);
bool SetApplicationUsers(string collectionName, RemoteApplication remoteApp, string[] users);
} }
} }

View file

@ -496,6 +496,70 @@ namespace WebsitePanel.Providers.RemoteDesktopServices
#region Remote Applications #region Remote Applications
public string[] GetApplicationUsers(string collectionName, string applicationName)
{
Runspace runspace = null;
List<string> result = new List<string>();
try
{
runspace = OpenRunspace();
Command cmd = new Command("Get-RDRemoteApp");
cmd.Parameters.Add("CollectionName", collectionName);
cmd.Parameters.Add("ConnectionBroker", ConnectionBroker);
cmd.Parameters.Add("DisplayName", applicationName);
var application = ExecuteShellCommand(runspace, cmd, false).FirstOrDefault();
if (application != null)
{
var users = (string[])(GetPSObjectProperty(application, "UserGroups"));
if (users != null)
{
result.AddRange(users);
}
}
}
finally
{
CloseRunspace(runspace);
}
return result.ToArray();
}
public bool SetApplicationUsers(string collectionName, RemoteApplication remoteApp, string[] users)
{
Runspace runspace = null;
bool result = true;
try
{
runspace = OpenRunspace();
Command cmd = new Command("Set-RDRemoteApp");
cmd.Parameters.Add("CollectionName", collectionName);
cmd.Parameters.Add("ConnectionBroker", ConnectionBroker);
cmd.Parameters.Add("DisplayName", remoteApp.DisplayName);
cmd.Parameters.Add("UserGroups", users);
cmd.Parameters.Add("Alias", remoteApp.Alias);
ExecuteShellCommand(runspace, cmd, false).FirstOrDefault();
}
catch(Exception)
{
result = false;
}
finally
{
CloseRunspace(runspace);
}
return result;
}
public List<StartMenuApp> GetAvailableRemoteApplications(string collectionName) public List<StartMenuApp> GetAvailableRemoteApplications(string collectionName)
{ {
var startApps = new List<StartMenuApp>(); var startApps = new List<StartMenuApp>();

View file

@ -65,6 +65,10 @@ namespace WebsitePanel.Providers.RemoteDesktopServices {
private System.Threading.SendOrPostCallback CheckServerAvailabilityOperationCompleted; private System.Threading.SendOrPostCallback CheckServerAvailabilityOperationCompleted;
private System.Threading.SendOrPostCallback GetApplicationUsersOperationCompleted;
private System.Threading.SendOrPostCallback SetApplicationUsersOperationCompleted;
/// <remarks/> /// <remarks/>
public RemoteDesktopServices() { public RemoteDesktopServices() {
this.Url = "http://localhost:9003/RemoteDesktopServices.asmx"; this.Url = "http://localhost:9003/RemoteDesktopServices.asmx";
@ -124,6 +128,12 @@ namespace WebsitePanel.Providers.RemoteDesktopServices {
/// <remarks/> /// <remarks/>
public event CheckServerAvailabilityCompletedEventHandler CheckServerAvailabilityCompleted; public event CheckServerAvailabilityCompletedEventHandler CheckServerAvailabilityCompleted;
/// <remarks/>
public event GetApplicationUsersCompletedEventHandler GetApplicationUsersCompleted;
/// <remarks/>
public event SetApplicationUsersCompletedEventHandler SetApplicationUsersCompleted;
/// <remarks/> /// <remarks/>
[System.Web.Services.Protocols.SoapHeaderAttribute("ServiceProviderSettingsSoapHeaderValue")] [System.Web.Services.Protocols.SoapHeaderAttribute("ServiceProviderSettingsSoapHeaderValue")]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://smbsaas/websitepanel/server/CreateCollection", RequestNamespace="http://smbsaas/websitepanel/server/", ResponseNamespace="http://smbsaas/websitepanel/server/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://smbsaas/websitepanel/server/CreateCollection", RequestNamespace="http://smbsaas/websitepanel/server/", ResponseNamespace="http://smbsaas/websitepanel/server/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
@ -918,6 +928,99 @@ namespace WebsitePanel.Providers.RemoteDesktopServices {
} }
} }
/// <remarks/>
[System.Web.Services.Protocols.SoapHeaderAttribute("ServiceProviderSettingsSoapHeaderValue")]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://smbsaas/websitepanel/server/GetApplicationUsers", RequestNamespace="http://smbsaas/websitepanel/server/", ResponseNamespace="http://smbsaas/websitepanel/server/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public string[] GetApplicationUsers(string collectionName, string applicationName) {
object[] results = this.Invoke("GetApplicationUsers", new object[] {
collectionName,
applicationName});
return ((string[])(results[0]));
}
/// <remarks/>
public System.IAsyncResult BeginGetApplicationUsers(string collectionName, string applicationName, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("GetApplicationUsers", new object[] {
collectionName,
applicationName}, callback, asyncState);
}
/// <remarks/>
public string[] EndGetApplicationUsers(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult);
return ((string[])(results[0]));
}
/// <remarks/>
public void GetApplicationUsersAsync(string collectionName, string applicationName) {
this.GetApplicationUsersAsync(collectionName, applicationName, null);
}
/// <remarks/>
public void GetApplicationUsersAsync(string collectionName, string applicationName, object userState) {
if ((this.GetApplicationUsersOperationCompleted == null)) {
this.GetApplicationUsersOperationCompleted = new System.Threading.SendOrPostCallback(this.OnGetApplicationUsersOperationCompleted);
}
this.InvokeAsync("GetApplicationUsers", new object[] {
collectionName,
applicationName}, this.GetApplicationUsersOperationCompleted, userState);
}
private void OnGetApplicationUsersOperationCompleted(object arg) {
if ((this.GetApplicationUsersCompleted != null)) {
System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg));
this.GetApplicationUsersCompleted(this, new GetApplicationUsersCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState));
}
}
/// <remarks/>
[System.Web.Services.Protocols.SoapHeaderAttribute("ServiceProviderSettingsSoapHeaderValue")]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://smbsaas/websitepanel/server/SetApplicationUsers", RequestNamespace="http://smbsaas/websitepanel/server/", ResponseNamespace="http://smbsaas/websitepanel/server/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public bool SetApplicationUsers(string collectionName, RemoteApplication remoteApp, string[] users) {
object[] results = this.Invoke("SetApplicationUsers", new object[] {
collectionName,
remoteApp,
users});
return ((bool)(results[0]));
}
/// <remarks/>
public System.IAsyncResult BeginSetApplicationUsers(string collectionName, RemoteApplication remoteApp, string[] users, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("SetApplicationUsers", new object[] {
collectionName,
remoteApp,
users}, callback, asyncState);
}
/// <remarks/>
public bool EndSetApplicationUsers(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult);
return ((bool)(results[0]));
}
/// <remarks/>
public void SetApplicationUsersAsync(string collectionName, RemoteApplication remoteApp, string[] users) {
this.SetApplicationUsersAsync(collectionName, remoteApp, users, null);
}
/// <remarks/>
public void SetApplicationUsersAsync(string collectionName, RemoteApplication remoteApp, string[] users, object userState) {
if ((this.SetApplicationUsersOperationCompleted == null)) {
this.SetApplicationUsersOperationCompleted = new System.Threading.SendOrPostCallback(this.OnSetApplicationUsersOperationCompleted);
}
this.InvokeAsync("SetApplicationUsers", new object[] {
collectionName,
remoteApp,
users}, this.SetApplicationUsersOperationCompleted, userState);
}
private void OnSetApplicationUsersOperationCompleted(object arg) {
if ((this.SetApplicationUsersCompleted != null)) {
System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg));
this.SetApplicationUsersCompleted(this, new SetApplicationUsersCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState));
}
}
/// <remarks/> /// <remarks/>
public new void CancelAsync(object userState) { public new void CancelAsync(object userState) {
base.CancelAsync(userState); base.CancelAsync(userState);
@ -1281,4 +1384,56 @@ namespace WebsitePanel.Providers.RemoteDesktopServices {
} }
} }
} }
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
public delegate void GetApplicationUsersCompletedEventHandler(object sender, GetApplicationUsersCompletedEventArgs e);
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
public partial class GetApplicationUsersCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs {
private object[] results;
internal GetApplicationUsersCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) :
base(exception, cancelled, userState) {
this.results = results;
}
/// <remarks/>
public string[] Result {
get {
this.RaiseExceptionIfNecessary();
return ((string[])(this.results[0]));
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
public delegate void SetApplicationUsersCompletedEventHandler(object sender, SetApplicationUsersCompletedEventArgs e);
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
public partial class SetApplicationUsersCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs {
private object[] results;
internal SetApplicationUsersCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) :
base(exception, cancelled, userState) {
this.results = results;
}
/// <remarks/>
public bool Result {
get {
this.RaiseExceptionIfNecessary();
return ((bool)(this.results[0]));
}
}
}
} }

View file

@ -359,6 +359,39 @@ namespace WebsitePanel.Server
throw; throw;
} }
} }
[WebMethod, SoapHeader("settings")]
public string[] GetApplicationUsers(string collectionName, string applicationName)
{
try
{
Log.WriteStart("'{0}' GetApplicationUsers", ProviderSettings.ProviderName);
var result = RDSProvider.GetApplicationUsers(collectionName, applicationName);
Log.WriteEnd("'{0}' GetApplicationUsers", ProviderSettings.ProviderName);
return result;
}
catch (Exception ex)
{
Log.WriteError(String.Format("'{0}' GetApplicationUsers", ProviderSettings.ProviderName), ex);
throw;
}
} }
[WebMethod, SoapHeader("settings")]
public bool SetApplicationUsers(string collectionName, RemoteApplication remoteApp, string[] users)
{
try
{
Log.WriteStart("'{0}' SetApplicationUsers", ProviderSettings.ProviderName);
var result = RDSProvider.SetApplicationUsers(collectionName, remoteApp, users);
Log.WriteEnd("'{0}' SetApplicationUsers", ProviderSettings.ProviderName);
return result;
}
catch (Exception ex)
{
Log.WriteError(String.Format("'{0}' SetApplicationUsers", ProviderSettings.ProviderName), ex);
throw;
}
}
}
} }

View file

@ -141,5 +141,6 @@
<Control key="rds_create_collection" general_key="rds_collections" /> <Control key="rds_create_collection" general_key="rds_collections" />
<Control key="rds_collection_edit_apps" general_key="rds_collections" /> <Control key="rds_collection_edit_apps" general_key="rds_collections" />
<Control key="rds_collection_edit_users" general_key="rds_collections" /> <Control key="rds_collection_edit_users" general_key="rds_collections" />
<Control key="rds_application_edit_users" general_key="rds_collections" />
<Control key="rds_edit_collection" general_key="rds_collections" /> <Control key="rds_edit_collection" general_key="rds_collections" />
</Controls> </Controls>

View file

@ -574,6 +574,7 @@
<Control key="rds_create_collection" src="WebsitePanel/RDS/RDSCreateCollection.ascx" title="RDSCreateCollection" type="View" /> <Control key="rds_create_collection" src="WebsitePanel/RDS/RDSCreateCollection.ascx" title="RDSCreateCollection" type="View" />
<Control key="rds_collection_edit_apps" src="WebsitePanel/RDS/RDSEditCollectionApps.ascx" title="RDSEditCollectionApps" type="View" /> <Control key="rds_collection_edit_apps" src="WebsitePanel/RDS/RDSEditCollectionApps.ascx" title="RDSEditCollectionApps" type="View" />
<Control key="rds_collection_edit_users" src="WebsitePanel/RDS/RDSEditCollectionUsers.ascx" title="RDSEditCollectionUsers" type="View" /> <Control key="rds_collection_edit_users" src="WebsitePanel/RDS/RDSEditCollectionUsers.ascx" title="RDSEditCollectionUsers" type="View" />
<Control key="rds_application_edit_users" src="WebsitePanel/RDS/RDSEditApplicationUsers.ascx" title="RDSEditApplicationUsers" type="View" />
<Control key="rds_edit_collection" src="WebsitePanel/RDS/RDSEditCollection.ascx" title="RDSEditCollection" type="View" /> <Control key="rds_edit_collection" src="WebsitePanel/RDS/RDSEditCollection.ascx" title="RDSEditCollection" type="View" />
</Controls> </Controls>
</ModuleDefinition> </ModuleDefinition>

View file

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="btnCreate.OnClientClick" xml:space="preserve">
<value>ShowProgressDialog('Adding RDS Server ...');</value>
</data>
<data name="btnSave.Text" xml:space="preserve">
<value>Save Changes</value>
</data>
<data name="FormComments.Text" xml:space="preserve">
<value />
</data>
<data name="Text.PageName" xml:space="preserve">
<value>Edit RDS Application</value>
</data>
<data name="locTitle.Text" xml:space="preserve">
<value>Edit RDS Application</value>
</data>
<data name="locCollectionName.Text" xml:space="preserve">
<value>Application Name:</value>
</data>
<data name="gvRDSServerName.Header" xml:space="preserve">
<value>Server Name</value>
</data>
<data name="gvRDSServers.Empty" xml:space="preserve">
<value>No RDS Servers have been added yet. To add a new RDS Servers click "Add RDS Server" button.</value>
</data>
</root>

View file

@ -0,0 +1,46 @@
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="RDSEditApplicationUsers.ascx.cs" Inherits="WebsitePanel.Portal.RDS.RDSEditApplicationUsers" %>
<%@ Register Src="../UserControls/SimpleMessageBox.ascx" TagName="SimpleMessageBox" TagPrefix="wsp" %>
<%@ Register Src="../UserControls/EnableAsyncTasksSupport.ascx" TagName="EnableAsyncTasksSupport" TagPrefix="wsp" %>
<%@ Register Src="UserControls/RDSCollectionUsers.ascx" TagName="CollectionUsers" TagPrefix="wsp"%>
<script type="text/javascript" src="/JavaScript/jquery.min.js?v=1.4.4"></script>
<wsp:EnableAsyncTasksSupport id="asyncTasks" runat="server"/>
<div id="ExchangeContainer">
<div class="Module">
<div class="Left">
</div>
<div class="Content">
<div class="Center">
<div class="Title">
<asp:Image ID="imgEditRDSCollection" SkinID="EnterpriseStorageSpace48" runat="server" />
<asp:Localize ID="locTitle" runat="server" meta:resourcekey="locTitle" Text="Edit RDS Application"></asp:Localize>
</div>
<div class="FormContentRDS">
<wsp:SimpleMessageBox id="messageBox" runat="server" />
<table>
<tr>
<td class="FormLabel150" style="width: 100px;"><asp:Localize ID="locApplicationName" runat="server" meta:resourcekey="locApplicationName" Text="Collection Name:"></asp:Localize></td>
<td class="FormLabel150">
<asp:Localize ID="locCName" runat="server" Text="" />
</td>
</tr>
</table>
<fieldset id="UsersPanel" runat="server">
<legend><asp:Localize ID="locUsersSection" runat="server" meta:resourcekey="locUsersSection" Text="Users"></asp:Localize></legend>
<div style="padding: 10px;">
<wsp:CollectionUsers id="users" runat="server" />
</div>
</fieldset>
<div class="FormFooter">
<asp:Button id="btnSave" runat="server" Text="Save" CssClass="Button1" meta:resourcekey="btnSave" ValidationGroup="SaveRDSCollectoin" OnClick="btnSave_Click"></asp:Button>
<asp:ValidationSummary ID="valSummary" runat="server" ShowMessageBox="True" ShowSummary="False" ValidationGroup="SaveRDSCollectoin" />
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using WebsitePanel.Providers.RemoteDesktopServices;
namespace WebsitePanel.Portal.RDS
{
public partial class RDSEditApplicationUsers : WebsitePanelModuleBase
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
var collection = ES.Services.RDS.GetRdsCollection(PanelRequest.CollectionID);
var applications = ES.Services.RDS.GetCollectionRemoteApplications(PanelRequest.ItemID, collection.Name);
var remoteApp = applications.Where(x => x.DisplayName.Equals(PanelRequest.ApplicationID, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
var collectionUsers = ES.Services.RDS.GetRdsCollectionUsers(PanelRequest.CollectionID);
var applicationUsers = ES.Services.RDS.GetApplicationUsers(PanelRequest.ItemID, PanelRequest.CollectionID, remoteApp);
locCName.Text = collection.Name;
users.SetUsers(collectionUsers.Where(x => applicationUsers.Contains(x.SamAccountName)).ToArray());
}
}
protected void btnSave_Click(object sender, EventArgs e)
{
if (!Page.IsValid)
{
return;
}
try
{
var collection = ES.Services.RDS.GetRdsCollection(PanelRequest.CollectionID);
var applications = ES.Services.RDS.GetCollectionRemoteApplications(PanelRequest.ItemID, collection.Name);
var remoteApp = applications.Where(x => x.DisplayName.Equals(PanelRequest.ApplicationID, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
ES.Services.RDS.SetApplicationUsers(PanelRequest.ItemID, PanelRequest.CollectionID, remoteApp, users.GetUsers().Select(x => x.AccountName).ToArray());
Response.Redirect(EditUrl("SpaceID", PanelSecurity.PackageId.ToString(), "rds_collection_edit_apps", "CollectionId=" + PanelRequest.CollectionID, "ItemID=" + PanelRequest.ItemID));
}
catch (Exception)
{
}
}
}
}

View file

@ -0,0 +1,114 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace WebsitePanel.Portal.RDS {
public partial class RDSEditApplicationUsers {
/// <summary>
/// asyncTasks control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::WebsitePanel.Portal.EnableAsyncTasksSupport asyncTasks;
/// <summary>
/// imgEditRDSCollection control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Image imgEditRDSCollection;
/// <summary>
/// locTitle control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Localize locTitle;
/// <summary>
/// messageBox control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::WebsitePanel.Portal.UserControls.SimpleMessageBox messageBox;
/// <summary>
/// locApplicationName control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Localize locApplicationName;
/// <summary>
/// locCName control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Localize locCName;
/// <summary>
/// UsersPanel control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.HtmlControls.HtmlGenericControl UsersPanel;
/// <summary>
/// locUsersSection control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Localize locUsersSection;
/// <summary>
/// users control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::WebsitePanel.Portal.RDS.UserControls.RDSCollectionUsers users;
/// <summary>
/// btnSave control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Button btnSave;
/// <summary>
/// valSummary control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.ValidationSummary valSummary;
}
}

View file

@ -48,7 +48,7 @@ namespace WebsitePanel.Portal.RDS
locCName.Text = collection.Name; locCName.Text = collection.Name;
remoreApps.SetApps(collectionApps); remoreApps.SetApps(collectionApps, Module);
} }
} }

View file

@ -21,13 +21,18 @@
<ItemStyle Width="10px" /> <ItemStyle Width="10px" />
</asp:TemplateField> </asp:TemplateField>
<asp:TemplateField meta:resourcekey="gvAppName" HeaderText="gvAppName"> <asp:TemplateField meta:resourcekey="gvAppName" HeaderText="gvAppName">
<ItemStyle Width="60%" Wrap="false"> <ItemStyle Width="90%" Wrap="false">
</ItemStyle> </ItemStyle>
<ItemTemplate> <ItemTemplate>
<asp:Literal ID="litDisplayName" runat="server" Text='<%# Eval("DisplayName") %>'></asp:Literal> <asp:Literal ID="litDisplayName" runat="server" Text='<%# Eval("DisplayName") %>'></asp:Literal>
<asp:HiddenField ID="hfFilePath" runat="server" Value='<%# Eval("FilePath") %>'/> <asp:HiddenField ID="hfFilePath" runat="server" Value='<%# Eval("FilePath") %>'/>
</ItemTemplate> </ItemTemplate>
</asp:TemplateField> </asp:TemplateField>
<asp:TemplateField>
<ItemTemplate>
<asp:hyperlink id="lnkUsers" meta:resourcekey="lnkUsers" runat="server" NavigateUrl='<%# GetCollectionUsersEditUrl(Eval("DisplayName").ToString()) %>'>Users</asp:hyperlink>
</ItemTemplate>
</asp:TemplateField>
</Columns> </Columns>
</asp:GridView> </asp:GridView>
<br /> <br />

View file

@ -54,6 +54,12 @@ namespace WebsitePanel.Portal.RDS.UserControls
BindApps(apps, false); BindApps(apps, false);
} }
public void SetApps(RemoteApplication[] apps, WebPortal.PageModule module)
{
Module = module;
BindApps(apps, false);
}
public RemoteApplication[] GetApps() public RemoteApplication[] GetApps()
{ {
return GetGridViewApps(SelectedState.All).ToArray(); return GetGridViewApps(SelectedState.All).ToArray();
@ -218,5 +224,11 @@ namespace WebsitePanel.Portal.RDS.UserControls
{ {
return string.Compare(app1.DisplayName, app2.DisplayName); return string.Compare(app1.DisplayName, app2.DisplayName);
} }
public string GetCollectionUsersEditUrl(string appId)
{
return EditUrl("SpaceID", PanelSecurity.PackageId.ToString(), "rds_application_edit_users",
"CollectionId=" + PanelRequest.CollectionID, "ItemID=" + PanelRequest.ItemID, "ApplicationID=" + appId);
}
} }
} }

View file

@ -246,6 +246,13 @@
<Compile Include="RDS\AddRDSServer.ascx.designer.cs"> <Compile Include="RDS\AddRDSServer.ascx.designer.cs">
<DependentUpon>AddRDSServer.ascx</DependentUpon> <DependentUpon>AddRDSServer.ascx</DependentUpon>
</Compile> </Compile>
<Compile Include="RDS\RDSEditApplicationUsers.ascx.cs">
<DependentUpon>RDSEditApplicationUsers.ascx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="RDS\RDSEditApplicationUsers.ascx.designer.cs">
<DependentUpon>RDSEditApplicationUsers.ascx</DependentUpon>
</Compile>
<Compile Include="RDS\RDSEditCollection.ascx.cs"> <Compile Include="RDS\RDSEditCollection.ascx.cs">
<DependentUpon>RDSEditCollection.ascx</DependentUpon> <DependentUpon>RDSEditCollection.ascx</DependentUpon>
<SubType>ASPXCodeBehind</SubType> <SubType>ASPXCodeBehind</SubType>
@ -4282,6 +4289,7 @@
<Content Include="RDSServers.ascx" /> <Content Include="RDSServers.ascx" />
<Content Include="RDS\AssignedRDSServers.ascx" /> <Content Include="RDS\AssignedRDSServers.ascx" />
<Content Include="RDS\AddRDSServer.ascx" /> <Content Include="RDS\AddRDSServer.ascx" />
<Content Include="RDS\RDSEditApplicationUsers.ascx" />
<Content Include="RDS\RDSEditCollection.ascx" /> <Content Include="RDS\RDSEditCollection.ascx" />
<Content Include="RDS\RDSEditCollectionApps.ascx" /> <Content Include="RDS\RDSEditCollectionApps.ascx" />
<Content Include="RDS\RDSEditCollectionUsers.ascx" /> <Content Include="RDS\RDSEditCollectionUsers.ascx" />
@ -4297,6 +4305,9 @@
<Content Include="RDS\App_LocalResources\RDSEditCollection.ascx.resx"> <Content Include="RDS\App_LocalResources\RDSEditCollection.ascx.resx">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Content> </Content>
<Content Include="RDS\App_LocalResources\RDSEditApplicationUsers.ascx.resx">
<SubType>Designer</SubType>
</Content>
<EmbeddedResource Include="ScheduleTaskControls\App_LocalResources\DomainLookupView.ascx.resx"> <EmbeddedResource Include="ScheduleTaskControls\App_LocalResources\DomainLookupView.ascx.resx">
<Generator>ResXFileCodeGenerator</Generator> <Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>DomainLookupView.ascx.Designer.cs</LastGenOutput> <LastGenOutput>DomainLookupView.ascx.Designer.cs</LastGenOutput>
@ -5607,7 +5618,9 @@
<Content Include="RDS\App_LocalResources\RDSCreateCollection.ascx.resx"> <Content Include="RDS\App_LocalResources\RDSCreateCollection.ascx.resx">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Content> </Content>
<Content Include="RDS\App_LocalResources\RDSEditCollectionUsers.ascx.resx" /> <Content Include="RDS\App_LocalResources\RDSEditCollectionUsers.ascx.resx">
<SubType>Designer</SubType>
</Content>
<Content Include="RDS\App_LocalResources\RDSEditCollectionApps.ascx.resx" /> <Content Include="RDS\App_LocalResources\RDSEditCollectionApps.ascx.resx" />
<Content Include="RDS\UserControls\App_LocalResources\RDSCollectionUsers.ascx.resx" /> <Content Include="RDS\UserControls\App_LocalResources\RDSCollectionUsers.ascx.resx" />
<Content Include="RDS\UserControls\App_LocalResources\RDSCollectionServers.ascx.resx" /> <Content Include="RDS\UserControls\App_LocalResources\RDSCollectionServers.ascx.resx" />