public Collection<PSObject> RunPowerShell(Dictionary<string, object> arguments, LoginViewModel clientId) { // Start with identity assigned by IIS Application Pool var poolIdentity = System.Security.Principal.WindowsIdentity.GetCurrent(); // TODO: remove, this is for initial testing //scriptPath = Path.Combine(DT2.Properties.Settings.Default.PowerShellScriptsFolder, "TestScript.ps1"); Command command = new Command(scriptPath); if (arguments != null) { foreach (var argument in arguments) { command.Parameters.Add(argument.Key, argument.Value); } } if (debug) { command.Parameters.Add("debug"); } command.Parameters.Add("log", logger); // add the ndc context string ndcContext = log4net.NDC.Pop(); log4net.NDC.Push(ndcContext); command.Parameters.Add("ndcContext", ndcContext); try { logger.Debug("Application Pool identity is " + poolIdentity.Name + ", but we will impersonate " + clientId.UserName); if (System.Security.SecurityContext.IsWindowsIdentityFlowSuppressed()) { logger.Error("PowerShell calls will fail, because IsWindowsIdentityFlowSuppressed true"); } // A RunSpace defines the operating system environment, which references HKCU\Environment // We create it now before impersonating an identity that might not have HKCU\Environment access // TODO: it may be possible to improve performance by using a RunSpacePool using (PowerShell powerShell = PowerShell.Create()) { Runspace runspace = RunspaceFactory.CreateRunspace(); runspace.Open(); powerShell.Runspace = runspace; Collection<PSObject> results; IntPtr handle; SafeTokenHandle _handle; /// Test: generate primary login token /// LOGON32_PROVIDER_DEFAULT = 0 /// logger.Debug("LogonUser call for " + clientId.UserNameNoDomain); bool logonSuccess = NativeMethods.LogonUser(clientId.UserNameNoDomain, clientId.DomainName, clientId.Password, (int) LogonType.Interactive, 0, out handle); _handle = new SafeTokenHandle(handle); if (!logonSuccess) { string errMsg = "LogonUser() for " + clientId.UserName + "failed - no handle for user credentials:" + Marshal.GetLastWin32Error(); throw new Win32Exception(errMsg); } // When 'using' block ends, the thread reverts back to previous Windows identity, // because under the hood WindowsImpersonationContext.Undo() is called by Dispose() try { using ( WindowsImpersonationContext wic = WindowsIdentity.Impersonate(_handle.DangerousGetHandle())) { // WindowsIdentity will have changed to match clientId var clientIdentity = System.Security.Principal.WindowsIdentity.GetCurrent(); logger.Debug("Application Pool identity is now " + clientIdentity.Name); powerShell.Commands.AddCommand(command); logger.Debug("Calling " + scriptPath); results = powerShell.Invoke(); } } // Back to the original identity finally { // dispose of the LogonUser handle _handle.Dispose(); // clean up RunSpace used by the PowerShell object runspace.Close(); runspace.Dispose(); } // The order is important here. Debug messages are flushed to the log *before* checking for errors // so the debug traces leading up to an error are not lost Collection<PSObject> filteredResults = new Collection<PSObject>(); foreach (PSObject result in results) { string output = result.BaseObject as string; if ((output != null) && output.StartsWith(DebugPrefix)) { if (debug) { logger.Info(output.Substring(DebugPrefix.Length)); } } else { filteredResults.Add(result); } } foreach (DebugRecord r in powerShell.Streams.Debug) { logger.Info(r.Message); } logger.Debug("Examining powershell error records"); foreach (ErrorRecord r in powerShell.Streams.Error) { // If the exception doesn't match a "to be ignored" exception, then throw it if (IgnoreExceptions.SingleOrDefault(i => i.Equals(r.Exception.GetType().FullName, StringComparison.InvariantCultureIgnoreCase)) == null) { logger.Error("Powershell reported exception:" + r.ErrorDetails); throw r.Exception; } } return filteredResults; } } catch (Exception e) { logger.Error(e); logger.Error(e.Message); logger.Error(e.StackTrace); throw; } }
public static string JsonSerialize(LoginViewModel that) { return Json.Encode(that); }
private static void RunLongScript(PsWrapper script, Dictionary <string, object> psargs, LoginViewModel clientId, string ndcContext, Action callAction = null) { using (log4net.NDC.Push(ndcContext)) { try { script.RunPowerShell(psargs, clientId); logger.Info("Completed RunPowerShell "); if (callAction != null) { logger.Info("Completed RunPowerShell, and calling callAction"); callAction(); } } catch (Exception e) { var errMsg = e.Message; logger.Error("In RunLongScript we saw exception with message " + errMsg); } } }
public static List <XenDesktopInventoryItem> GetActiveDirectoryUsers(string query) { var users = new List <XenDesktopInventoryItem>(); var grps = new List <XenDesktopInventoryItem>(); var usersFinal = new List <XenDesktopInventoryItem>(); LoginViewModel clientId = LoginViewModel.JsonDeserialize(((FormsIdentity)HttpContext.Current.User.Identity).Ticket); logger.Debug("Find users and groups in domain of " + clientId.UserName); string searchDomain = clientId.DomainName; try { PrincipalContext adController = new PrincipalContext(ContextType.Domain, searchDomain, clientId.UserName, clientId.Password); UserPrincipal user = new UserPrincipal(adController); user.SamAccountName = query; PrincipalSearcher usrSrch = new PrincipalSearcher(user); PrincipalSearchResult <Principal> userSearchResult = usrSrch.FindAll(); logger.Debug("Search for users netted " + userSearchResult.Count() + " results."); GroupPrincipal grp = new GroupPrincipal(adController); grp.SamAccountName = query; PrincipalSearcher grpSrch = new PrincipalSearcher(grp); PrincipalSearchResult <Principal> grpSearchResult = grpSrch.FindAll(); logger.Debug("Search for groups netted " + grpSearchResult.Count() + " results."); int grpCount = 20; foreach (var currGrp in grpSearchResult) { if (UsersSkipList.Contains(currGrp.SamAccountName)) { continue; } var userRsrc = new XenDesktopInventoryItem { Id = searchDomain + "\\" + currGrp.SamAccountName, Name = searchDomain + "\\" + currGrp.SamAccountName + "(group)" }; grps.Add(userRsrc); grpCount--; if (grpCount < 0) { break; } } grps.Sort(new Comparison <XenDesktopInventoryItem>(XenDesktopInventoryItem.Compare)); int userCount = 100; foreach (var currUser in userSearchResult) { if (UsersSkipList.Contains(currUser.SamAccountName)) { continue; } var userRsrc = new XenDesktopInventoryItem { Id = searchDomain + "\\" + currUser.SamAccountName, Name = searchDomain + "\\" + currUser.SamAccountName }; users.Add(userRsrc); userCount--; if (userCount < 0) { break; } } users.Sort(new Comparison <XenDesktopInventoryItem>(XenDesktopInventoryItem.Compare)); usersFinal = grps.Concat(users).ToList(); } catch (System.Exception ex) { var errmsg = "Problem searching for users in " + searchDomain + " Details: " + ex.Message; logger.Error(errmsg); logger.Error(ex); } return(usersFinal); }
/// <summary> /// The list of users associated with a DesktopGroup is in the DesktopGroupAccessPolicy. In our case, /// there are two per desktop group. One is used when accessing the group through an AG (Access Gateway). /// The other is used when accessing the desktop directly. /// </summary> /// <param name="catalogName"></param> /// <param name="results"></param> private static void GetBrokerCatalogUsers(string catalogName, List <Catalog> result) { string desktopGrpAccessPolicyName = string.IsNullOrEmpty(catalogName) ? null : catalogName + ScriptNames.DesktopGroupSuffix + ScriptNames.AccessPolicySuffixForDirect; Dictionary <string, object> psargs = new Dictionary <string, object>(); psargs.Add("desktopGroupPolicyName", desktopGrpAccessPolicyName); var listProvScript = new PsWrapper(Path.Combine(DT2.Properties.Settings.Default.PowerShellScriptsFolder, ScriptNames.GetDesktopGroupsAccessPolicy)); LoginViewModel clientId = LoginViewModel.JsonDeserialize(((FormsIdentity)HttpContext.Current.User.Identity).Ticket); var psProvs = listProvScript.RunPowerShell(psargs, clientId); // TODO: add the users to the list. foreach (PSObject item in psProvs) { dynamic includedUsers = item.Members["IncludedUsers"].Value; dynamic excludedUsers = item.Members["ExcludedUsers"].Value; string name = (string)item.Members["Name"].Value; // assertions if (includedUsers.Length < 1) { var errMsg = "DesktopGroup for broker catalog " + catalogName + " has no included users!"; logger.Error(errMsg); } if (excludedUsers.Length > 0) { var errMsg = "DesktopGroup for broker catalog " + catalogName + " *has* excluded users!"; logger.Error(errMsg); } List <string> userList = new List <string>(); foreach (dynamic user in includedUsers) { userList.Add(user.Name); } // Update corresponding Catalog int assertUsage = 0; foreach (var cat in result) { if (catalogName + ScriptNames.DesktopGroupSuffix + ScriptNames.AccessPolicySuffixForDirect == name) { assertUsage++; cat.Users = userList.ToArray(); } } // Make sure there is a value for 'Users' foreach (var cat in result) { if (cat.Users == null) { cat.Users = new string[0]; var infoMsg = "DesktopGroup for broker catalog " + catalogName + " has NO users!"; logger.Error(infoMsg); } } // Assert if (assertUsage > 1) { var errMsg = "Provisioning scheme used in multiple catalogs: name " + name; logger.Error(errMsg); } } // End foreach. }
public static void CreateCatalog(Catalog newCat) { try { var jsonEquiv = Newtonsoft.Json.JsonConvert.SerializeObject(newCat); logger.Info("Creating Catalog corresponding to " + Newtonsoft.Json.JsonConvert.ToString(jsonEquiv)); Dictionary <string, object> psargs = new Dictionary <string, object>(); psargs.Add("ddcAddress", DT2.Properties.Settings.Default.XenDesktopAdminAddress); psargs.Add("catalogName", newCat.Name); psargs.Add("catalogDesc", newCat.Description); psargs.Add("catalogSessionSupport", GetXenDesktopDesktopSessionSupport(newCat.DesktopType)); psargs.Add("desktopAllocationType", GetXenDesktopDesktopAllocationType(newCat.DesktopType)); psargs.Add("persistUserChanges", GetXenDesktopDesktopPersistUserChanges(newCat.DesktopType)); psargs.Add("desktopCleanOnBoot", GetXenDesktopDesktopCleanOnBoot(newCat.DesktopType)); psargs.Add("desktopDomain", DT2.Properties.Settings.Default.XenDesktopDomain); psargs.Add("templatePath", newCat.Template); psargs.Add("networkPath", newCat.Network); psargs.Add("hostingUnitName", DT2.Properties.Settings.Default.XenDesktopHostingUnitName); psargs.Add("controllerAddress", DT2.Properties.Settings.Default.XenDesktopDDC); logger.Info("Catalog has " + newCat.Users.Length + " AD accounts and " + newCat.Count + " machines."); psargs.Add("desktopCount", newCat.Count); psargs.Add("computerOffering", newCat.ComputeOffering); // Naming scheme: Remove spaces and other special characters from the pool name, truncate at 12 characters string desktopNamingScheme = newCat.Name.Replace(" ", string.Empty); desktopNamingScheme = desktopNamingScheme.Length > 12 ? desktopNamingScheme.Substring(0, 12) : desktopNamingScheme; desktopNamingScheme += "###"; psargs.Add("desktopNamingScheme", desktopNamingScheme); // Parameters required to setup the corresponding desktopgroup psargs.Add("userNames", newCat.Users); psargs.Add("desktopGrpName", newCat.Name + ScriptNames.DesktopGroupSuffix); psargs.Add("machineCount", newCat.Count); string secGrps = DT2.Properties.Settings.Default.SecurityGroups; secGrps = secGrps ?? String.Empty; if (!string.IsNullOrEmpty((newCat.SecurityGroup))) { secGrps = newCat.SecurityGroup; } psargs.Add("securityGroups", secGrps); var poshScript = new PsWrapper(Path.Combine(DT2.Properties.Settings.Default.PowerShellScriptsFolder, ScriptNames.CreateDesktopGroupScript)); var jsonEquiv2 = Newtonsoft.Json.JsonConvert.SerializeObject(psargs); logger.Info("Calling " + ScriptNames.CreateDesktopGroupScript + " with args: " + jsonEquiv2); var exToIgnore = "Citrix.Broker.Admin.SDK.SdkOperationException"; poshScript.IgnoreExceptions.Add(exToIgnore); logger.Info("Ignoring exceptions of type " + exToIgnore + " because the SDK generates them for Get-BrokerUser calls as part of normal operation."); // Assert if (newCat.DesktopType == PublishedDesktops) { if ("Random" != (string)psargs["desktopAllocationType"]) { var errMsg = "Wrong desktopAllocationType for " + newCat.Name + " of type " + newCat.DesktopType; throw new ArgumentException(errMsg); } if ("MultiSession" != (string)psargs["catalogSessionSupport"]) { var errMsg = "Wrong catalogSessionSupport for " + newCat.Name + " of type " + newCat.DesktopType; throw new ArgumentException(errMsg); } if ("Discard" != (string)psargs["persistUserChanges"]) { var errMsg = "Wrong persistUserChanges for " + newCat.Name + " of type " + newCat.DesktopType; throw new ArgumentException(errMsg); } if (psargs["desktopCleanOnBoot"] == null) { var errMsg = "Wrong desktopCleanOnBoot for " + newCat.Name + " of type " + newCat.DesktopType; throw new ArgumentException(errMsg); } } if (DT2.Properties.Settings.Default.TestDisableCatalogCreate) { logger.Warn("Skipping Catalog create, as TestDisableCatalogCreate is set true"); } else { // Enable Windows Authentication in ASP.NET *and* IIS to ensure User.Identity is a FormsIdentity LoginViewModel clientId = LoginViewModel.JsonDeserialize(((FormsIdentity)HttpContext.Current.User.Identity).Ticket); // add the ndc context string ndcContext = log4net.NDC.Pop(); log4net.NDC.Push(ndcContext); Task.Run(() => RunLongScript(poshScript, psargs, clientId, ndcContext, () => { EmailAdmin(newCat, clientId); })); } } catch (Exception e) { var errMsg = e.Message; logger.Error(errMsg); } }
private static void GetProvSchemeDetails(string catalogName, List <Catalog> result) { Dictionary <string, object> psargs = new Dictionary <string, object>(); psargs.Add("catalogName", catalogName); var listProvScript = new PsWrapper(Path.Combine(DT2.Properties.Settings.Default.PowerShellScriptsFolder, ScriptNames.GetProvSchemesScript)); LoginViewModel clientId = LoginViewModel.JsonDeserialize(((FormsIdentity)HttpContext.Current.User.Identity).Ticket); var psProvs = listProvScript.RunPowerShell(psargs, clientId); foreach (PSObject item in psProvs) { string provName = (string)item.Members["ProvisioningSchemeName"].Value; Guid provId = (Guid)item.Members["ProvisioningSchemeUid"].Value; string templatePath = (string)item.Members["MasterImageVM"].Value; string serviceOffering = (string)item.Members["ServiceOffering"].Value; string[] securityGroups = (item.Members["SecurityGroups"] == null ? new string[0] : (string[])item.Members["SecurityGroups"].Value); string securityGroup = securityGroups.Length > 0 ? securityGroups[0] : ""; // Assert if (templatePath == null) { var errMsg = "No template for provisioning scheme " + provName; logger.Error(errMsg); continue; } // Assert if (serviceOffering == null) { var errMsg = "No Desktop compute offering for provisioning scheme " + provName; logger.Error(errMsg); continue; } string[] templateSplitPath = templatePath.Split(new char[] { '\\' }); string template = templateSplitPath[templateSplitPath.Length - 1]; // Assert if (!template.EndsWith(".template")) { var errMsg = "Template ends with wrong extension, path is " + templatePath + " template name is " + template; logger.Error(errMsg); continue; } // Update corresponding Catalog int assertUsage = 0; foreach (var cat in result) { if (cat.ProvisioningSchemeId == provId.ToString()) { assertUsage++; cat.Template = template.Substring(0, template.Length - ".template".Length); cat.ComputeOffering = serviceOffering.Substring(0, serviceOffering.Length - ".serviceoffering".Length); cat.DiskSize = (int)item.Members["DiskSize"].Value; // TODO: ask SDK team for alternative to dyanamic variables dynamic networkList = item.Members["NetworkMaps"].Value; dynamic netDetails = networkList[0]; string networkPath = (string)netDetails.NetworkPath; string[] networkSplitPath = networkPath.Split(new char[] { '\\' }); string networkName = networkSplitPath[networkSplitPath.Length - 1]; cat.Network = networkName.Substring(0, networkName.Length - ".network".Length); cat.SecurityGroup = securityGroup; } } // Assert if (assertUsage > 1) { var errMsg = "Provisioning scheme used in multiple catalogs: name " + provName + " UUID " + provId; logger.Error(errMsg); } } // End foreach. }
// TODO: update to search in domain of logged-in user public static String GetActiveDirectoryUserEmail(string domainQualifiedSAMAccountName, LoginViewModel clientId) { var splitUserName = domainQualifiedSAMAccountName.Split(new char[] { '\\' }); var samAccountName = splitUserName[splitUserName.Length - 1]; String email = null; try { logger.Debug("Find users and groups in domain of " + clientId.UserName); PrincipalContext adController = new PrincipalContext(ContextType.Domain, clientId.DomainName, clientId.UserName, clientId.Password); // Could be a user or a group: UserPrincipal user = new UserPrincipal(adController); user.SamAccountName = samAccountName; PrincipalSearcher usrSrch = new PrincipalSearcher(user); Principal userSearchResult = usrSrch.FindOne(); if (userSearchResult == null) { GroupPrincipal grp = new GroupPrincipal(adController); grp.SamAccountName = samAccountName; PrincipalSearcher grpSrch = new PrincipalSearcher(grp); userSearchResult = grpSrch.FindOne(); } if (userSearchResult == null) { logger.Error("Could not find Principal in AD for " + domainQualifiedSAMAccountName); return(email); } DirectoryEntry dirEntry = (DirectoryEntry)userSearchResult.GetUnderlyingObject(); // TODO: is this property guaranteed to be present, e.g. if I create a user called donall, will the email property still be present? email = dirEntry.Properties["mail"] == null ? null : (string)(dirEntry.Properties["mail"].Value); if (email == null) { logger.Info("No email for user" + domainQualifiedSAMAccountName); } } catch (System.Exception ex) { var errmsg = "Problem searching for users. Details: " + ex.Message; logger.Error(errmsg); } return(email); }
// TODO: update to search in domain of logged-in user public static String GetActiveDirectoryUserEmail(string domainQualifiedSAMAccountName, LoginViewModel clientId) { var splitUserName = domainQualifiedSAMAccountName.Split(new char[] { '\\' }); var samAccountName = splitUserName[splitUserName.Length - 1]; String email = null; try { logger.Debug("Find users and groups in domain of " + clientId.UserName); PrincipalContext adController = new PrincipalContext(ContextType.Domain, clientId.DomainName, clientId.UserName, clientId.Password); // Could be a user or a group: UserPrincipal user = new UserPrincipal(adController); user.SamAccountName = samAccountName; PrincipalSearcher usrSrch = new PrincipalSearcher(user); Principal userSearchResult = usrSrch.FindOne(); if (userSearchResult == null) { GroupPrincipal grp = new GroupPrincipal(adController); grp.SamAccountName = samAccountName; PrincipalSearcher grpSrch = new PrincipalSearcher(grp); userSearchResult = grpSrch.FindOne(); } if (userSearchResult == null) { logger.Error("Could not find Principal in AD for " + domainQualifiedSAMAccountName); return email; } DirectoryEntry dirEntry = (DirectoryEntry)userSearchResult.GetUnderlyingObject(); // TODO: is this property guaranteed to be present, e.g. if I create a user called donall, will the email property still be present? email = dirEntry.Properties["mail"] == null ? null : (string)(dirEntry.Properties["mail"].Value); if (email == null) { logger.Info("No email for user" + domainQualifiedSAMAccountName); } } catch (System.Exception ex) { var errmsg = "Problem searching for users. Details: " + ex.Message; logger.Error(errmsg); } return email; }
private static void RunLongScript(PsWrapper script, Dictionary<string, object> psargs, LoginViewModel clientId, string ndcContext, Action callAction = null) { using (log4net.NDC.Push(ndcContext)) { try { script.RunPowerShell(psargs, clientId); logger.Info("Completed RunPowerShell "); if (callAction != null) { logger.Info("Completed RunPowerShell, and calling callAction"); callAction(); } } catch (Exception e) { var errMsg = e.Message; logger.Error("In RunLongScript we saw exception with message " + errMsg); } } }
public static void EmailAdmin(Catalog newCat, LoginViewModel clientId) { try { // Send an email to each of the users from the signed on admin var adminEmail = GetActiveDirectoryUserEmail(clientId.UserName, clientId); if (string.IsNullOrEmpty(adminEmail)) { logger.Error("No email for admin " + clientId.UserName); return; } logger.Info("Email " + clientId.UserName + " to say that " + newCat.Name + " is done. "); var userAddr = new System.Net.Mail.MailAddress(adminEmail); var mailMsg = new System.Net.Mail.MailMessage(); mailMsg.Subject = "Desktop Catalog " + newCat.Name + " is ready!"; mailMsg.Body = "Finished creating " + newCat.Name + ". You access it from " + DT2.Properties.Settings.Default.XenDesktopStoreFrontUrl.ToString(); mailMsg.From = userAddr; mailMsg.CC.Add(adminEmail); // Build list of users to send email to. foreach (var user in newCat.Users) { var userEmail = GetActiveDirectoryUserEmail(user, clientId); if (string.IsNullOrEmpty(adminEmail)) { logger.Error("No email for desktop group user " + user); continue; } logger.Debug("Alert to be sent to " + userEmail + " for " + user); mailMsg.To.Add(userEmail); } logger.Debug("Sending alert for catalog " + newCat.Name); Utils.Utils.SendEmail(mailMsg); } catch (Exception e) { var errMsg = "Could not email desktop group creation alert"; logger.Error(errMsg, e); } }