How to notify user when programatically adding them to a group - c#

I am adding members to a group with the code below.
My question is very simple :
When adding members with this code, the invited person does not get an email notifying them. However when doing the same from the UI there is an option to notify the user. How can I do that from the code?
public void UpdateGoupMembers(string groupName, List<string> loginNames)
{
using (var clientContext = new ClientContext(baseUrl))
{
clientContext.Credentials = credentials;
var web = clientContext.Web;
var group = web.SiteGroups.GetByName(groupName);
if (group != null)
{
foreach (var loginName in loginNames)
{
var user = web.EnsureUser(loginName);
if (user != null)
{
group.Users.AddUser(user);
}
}
var existingUsers = group.Users;
clientContext.Load(existingUsers, includes => includes.Include(
f => f.LoginName,
f => f.UserId,
f => f.PrincipalType,
f => f.Email,
f => f.Id));
clientContext.ExecuteQuery();
foreach (var existingUser in existingUsers)
{
var userName = existingUser.LoginName.Split('|')[2];
if (!loginNames.Contains(userName))
{
group.Users.RemoveByLoginName(existingUser.LoginName);
}
}
}
clientContext.ExecuteQuery();
}
}

I do not believe it is possible to send a Welcome Email when users are added to a group programatically based off of the method documentation.
However, you can use the CSOM library to send an email programatically to the specific user after adding them to the group. Send Emails via SharePoint CSOM will be a great reference on how to do this.

Related

Assign a License (Microsoft Graph API)

I'm trying to assign a license in C# via Graph API.
https://learn.microsoft.com/en-us/graph/api/user-assignlicense?view=graph-rest-1.0
Parameters:
addLicenses (AssignedLicense collection)
A collection of assignedLicense objects that specify the licenses to add. You can
disable plans associated with a license by setting the disabledPlans
property on an assignedLicense object.
removeLicenses (Guid collection)
A collection of GUIDs that identify the licenses to remove.
Here is my code ...
var userQuery = await client.Users
.Request()
.Filter("userPrincipalName eq 'xxx#xxx.de'")
.GetAsync();
var user = userQuery.FirstOrDefault();
var skus = await client.SubscribedSkus.Request().GetAsync();
do
{
foreach (var sku in skus)
{
AssignedLicense aLicense = new AssignedLicense { SkuId = sku.SkuId };
IList<AssignedLicense> licensesToAdd = new AssignedLicense[] { aLicense };
IList<Guid> licensesToRemove = new Guid[] { };
try
{
client.Users[user.Id].AssignLicense(licensesToAdd, licensesToRemove);
}
}
}
while (skus.NextPageRequest != null && (skus = await skus.NextPageRequest.GetAsync()).Count > 0);
I don't get an error, but it does not work. The user has no license ...
I think you forgot something here:
client.Users[user.Id].AssignLicense(licensesToAdd, licensesToRemove);
I think it should be:
await client.Users[user.Id].AssignLicense(licensesToAdd, licensesToRemove).Request().PostAsync();
PS:
You can get the user with less code like:
var userQuery = await client.Users["xxx#xxx.de"].Request().GetAsync();

Retrieve items from a SharePoint list

I have searched this question on the web but to no result that fits my problem.
I am trying to retrive a specific colum in a sharepoint online list and comparing it to the currently logged in user. this is my code so far.
var spContext = SharePointContextProvider.Current.GetSharePointContext((HttpContext));
using (var clientContext = spContext.CreateUserClientContextForSPHost())
{
if (clientContext != null)
{
var user = GetUser(clientContext);
var items = LoadList(clientContext);
foreach (ListItem listItem in items)
{
if (user.Title == listItem["Author"].ToString())
{
//Do stuff later
}
}
}
}
public ListItemCollection LoadList(ClientContext context)
{
List announcementsList = context.Web.Lists.GetByTitle("Arbetsplan");
CamlQuery query = CamlQuery.CreateAllItemsQuery();
ListItemCollection items = announcementsList.GetItems(query);
context.Load(items);
context.ExecuteQuery();
return items;
}
public User GetUser(ClientContext context)
{
var user = context.Web.CurrentUser;
context.Load(user);
context.ExecuteQuery();
return user;
}
The error I get is like this:
The
Author column is of type
Person or Group. I have an idea that that's where the problem is. I am not handling the type correctly?
The Author field in the SharePoint list is named "Created By". But to reach it I have to use the intrenal name.

Code to list all permissions for SharePoint Folders

I'm after some C# code to recursively enumerate all the folders in a SharePoint web site and list the permissions applying to them to be run from a Sharepoint client machine. Can anyone provide or point me to an example?
The following code can perform this function on a server using SPSite object ( from https://social.msdn.microsoft.com/Forums/sqlserver/en-US/8c7c5735-039e-4cb9-a2b5-58d70a10793f/get-permissions-group-from-folders-tree-view-on-a-doc-library?forum=sharepointdevelopmentprevious) but I need to run it using SharePoint Client code
public static void getPermissionsOfFolders()
{
using (SPSite site = new SPSite("http://sp"))
{
using (SPWeb web = site.RootWeb)
{
SPList list = web.GetList("/Lists/List2");
foreach (SPListItem item in list.Folders)
{
Console.WriteLine("ID: "+item["ID"]+"--"+item.SortType);
if (SPFileSystemObjectType.Folder == item.SortType)
{
SPRoleAssignmentCollection roles = item.RoleAssignments;
foreach (SPRoleAssignment role in roles)
{
Console.WriteLine("~");
Console.WriteLine("Name: "+role.Member.Name);
SPRoleDefinitionBindingCollection bindings = role.RoleDefinitionBindings;
XmlDocument doc = new XmlDocument();
doc.LoadXml(bindings.Xml);
XmlNodeList itemList = doc.DocumentElement.SelectNodes("Role");
foreach (XmlNode currNode in itemList)
{
string s = currNode.Attributes["Name"].Value.ToString();
Console.WriteLine("Permission Level: "+s);
}
}
Console.WriteLine("--------------------------------------");
}
}
}
}
}
Code below fails with exception "Property ListItemAllFields not found" as shown below on clientContext.ExecuteQuery()
private void ListSPPermissions3()
{
string sSite = "http://server2012a/sites/TestDocs/";
using (var clientContext = new ClientContext(sSite))
{
Site site = clientContext.Site;
Web web = clientContext.Web;
List list = web.Lists.GetByTitle("Shared Documents");
clientContext.Load(list.RootFolder.Folders); //load the client object list.RootFolder.Folders
clientContext.ExecuteQuery();
int FolderCount = list.RootFolder.Folders.Count;
foreach (Microsoft.SharePoint.Client.Folder folder in list.RootFolder.Folders)
{
RoleAssignmentCollection roleAssCol = folder.ListItemAllFields.RoleAssignments;
clientContext.Load(roleAssCol);
clientContext.ExecuteQuery(); // Exception property ListItemAllFields not found
foreach (RoleAssignment roleAss in roleAssCol)
{
Console.WriteLine(roleAss.Member.Title);
}
}
}
}
There are at least the following flaws with your example:
The specified example only allows to retrieve folders one level
beneath:
clientContext.Load(list.RootFolder.Folders); //load the client object list.RootFolder.Folders
clientContext.ExecuteQuery();
Role assignments could be retrieved using a single request to the
server (see the below example), hence there is no need to perform
multiple requests to retrieve role assignments per folder.
Folder.ListItemAllFields property is supported only in SharePoint
2013 CSOM API
Having said that i would recommend to consider the following example to enumerate folder permissions:
using (var ctx = new ClientContext(webUri))
{
var list = ctx.Web.Lists.GetByTitle(listTitle);
var items = list.GetItems(CamlQuery.CreateAllFoldersQuery());
ctx.Load(items, icol => icol.Include(i => i.RoleAssignments.Include( ra => ra.Member), i => i.DisplayName ));
ctx.ExecuteQuery();
foreach (var item in items)
{
Console.WriteLine("{0} folder permissions",item.DisplayName);
foreach (var assignment in item.RoleAssignments)
{
Console.WriteLine(assignment.Member.Title);
}
}
}
The error is probably because the SharePoint SDK you are using is pre-SharePoint 2013 CSOM.
Folder.ListItemAllFields
property is available in SharePoint 2013 CSOM
For SharePoint 2010, you have to access folders like list items
ListItem item = context.Web.Lists.GetByTitle("Shared Documents").GetItemById(<item ID>);
and then get the RoleAssignments for the items.

Retrieve Lync contact from a phone number in an UCMA application

I have a C# managed Application that runs on a Lync 2013 Server and uses MSPL. I route every call from MSPL to the application and handle it there. Lync to Lync calls work fine and their to Header is in the form sip:user#domain.com. But when a call from outside the network (non-lync like mobile phone etc.) to the workphone of a Lyncuser is started, the Uri is like sip:+12341234#domain.com;user=phone (sip:[workphone]#domain). Passing this string to the Presence Retrieval function does not work.
var sips = new string[] { phone }; // The "To" number
presenceService.BeginPresenceQuery(sips, categories, null, null, null);
This always returns an empty result. How can I first retrieve the user associated with the phone number to get its presence?
I solved it this way:
public static UserObject FindContactBySip(string sip)
{
return UserList.FirstOrDefault(u => u.HasSip(sip));
}
private static void InitFindUsersInAD()
{
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
var user = new UserPrincipal(ctx);
user.Name = "*";
var searcher = new PrincipalSearcher(user);
var result = searcher.FindAll();
var sipList = new List<string>();
UserList = new List<UserObject>();
foreach (var res in result)
{
var underlying = (DirectoryEntry)res.GetUnderlyingObject();
string email = string.Empty, phone = string.Empty, policies = string.Empty;
foreach (var keyval in underlying.Properties.Values)
{
var kv = keyval as System.DirectoryServices.PropertyValueCollection;
if (kv != null && kv.Value is string)
{
if (kv.PropertyName.Equals("msRTCSIP-PrimaryUserAddress"))
{
email = (kv.Value ?? string.Empty).ToString();
}
else if (kv.PropertyName.Equals("msRTCSIP-Line"))
{
phone = (kv.Value ?? string.Empty).ToString();
}
else if (kv.PropertyName.Equals("msRTCSIP-UserPolicies"))
{
policies = (kv.Value ?? string.Empty).ToString();
}
}
}
if (!string.IsNullOrEmpty(phone) && !string.IsNullOrEmpty(email))
{
var userobj = new UserObject(email, phone, policies);
UserList.Add(userobj);
}
}
}
First I initialize the UserList (List // Custom class) from the AD. Then I call FindContactBySip and check if the provided SIP equals the Email or Phone of the User.
I have found two other ways to solve your problem.
In MSPL you can:
toContactCardInfo = QueryCategory(toUserUri, 0, "contactCard", 0);
Which gives you:
<contactCard xmlns=""http://schemas.microsoft.com/2006/09/sip/contactcard"" >
<identity >
<name >
<displayName >
Lync User</displayName>
</name>
<email >
lync.user#xxx.com</email>
</identity>
</contactCard>
You can turn the email address into a sip address. This only works if your lync setup uses email address for sip addresses.
The other method is to use 'P-Asserted-Identity' sip header to determine who the phone call is being routed to/from. The only problem is that it doesn't show up in the inital invites (as that would be for the From side anyway), but in the 180 ringing response from the Lync Client.
P-Asserted-Identity: <sip:lync.user#xxx.com>, <tel:+123456789;ext=12345>
So if you wait for the 180 ringing response then I would recommand that you use P-Asserted-Identity method and you don't even need to escape out of MSPL for it!

How to get the groups of a user in Active Directory? (c#, asp.net)

I use this code to get the groups of the current user. But I want to manually give the user and then get his groups. How can I do this?
using System.Security.Principal;
public ArrayList Groups()
{
ArrayList groups = new ArrayList();
foreach (IdentityReference group in System.Web.HttpContext.Current.Request.LogonUserIdentity.Groups)
{
groups.Add(group.Translate(typeof(NTAccount)).ToString());
}
return groups;
}
If you're on .NET 3.5 or up, you can use the new System.DirectoryServices.AccountManagement (S.DS.AM) namespace which makes this a lot easier than it used to be.
Read all about it here: Managing Directory Security Principals in the .NET Framework 3.5
Update: older MSDN magazine articles aren't online anymore, unfortunately - you'll need to download the CHM for the January 2008 MSDN magazine from Microsoft and read the article in there.
Basically, you need to have a "principal context" (typically your domain), a user principal, and then you get its groups very easily:
public List<GroupPrincipal> GetGroups(string userName)
{
List<GroupPrincipal> result = new List<GroupPrincipal>();
// establish domain context
PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain);
// find your user
UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, userName);
// if found - grab its groups
if(user != null)
{
PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups();
// iterate over all groups
foreach(Principal p in groups)
{
// make sure to add only group principals
if(p is GroupPrincipal)
{
result.Add((GroupPrincipal)p);
}
}
}
return result;
}
and that's all there is! You now have a result (a list) of authorization groups that user belongs to - iterate over them, print out their names or whatever you need to do.
Update: In order to access certain properties, which are not surfaced on the UserPrincipal object, you need to dig into the underlying DirectoryEntry:
public string GetDepartment(Principal principal)
{
string result = string.Empty;
DirectoryEntry de = (principal.GetUnderlyingObject() as DirectoryEntry);
if (de != null)
{
if (de.Properties.Contains("department"))
{
result = de.Properties["department"][0].ToString();
}
}
return result;
}
Update #2: seems shouldn't be too hard to put these two snippets of code together.... but ok - here it goes:
public string GetDepartment(string username)
{
string result = string.Empty;
// if you do repeated domain access, you might want to do this *once* outside this method,
// and pass it in as a second parameter!
PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain);
// find the user
UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, username);
// if user is found
if(user != null)
{
// get DirectoryEntry underlying it
DirectoryEntry de = (user.GetUnderlyingObject() as DirectoryEntry);
if (de != null)
{
if (de.Properties.Contains("department"))
{
result = de.Properties["department"][0].ToString();
}
}
}
return result;
}
GetAuthorizationGroups() does not find nested groups. To really get all groups a given user is a member of (including nested groups), try this:
using System.Security.Principal
private List<string> GetGroups(string userName)
{
List<string> result = new List<string>();
WindowsIdentity wi = new WindowsIdentity(userName);
foreach (IdentityReference group in wi.Groups)
{
try
{
result.Add(group.Translate(typeof(NTAccount)).ToString());
}
catch (Exception ex) { }
}
result.Sort();
return result;
}
I use try/catch because I had some exceptions with 2 out of 200 groups in a very large AD because some SIDs were no longer available. (The Translate() call does a SID -> Name conversion.)
First of all, GetAuthorizationGroups() is a great function but unfortunately has 2 disadvantages:
Performance is poor, especially in big company's with many users and groups. It fetches a lot more data then you actually need and does a server call for each loop iteration in the result
It contains bugs which can cause your application to stop working 'some day' when groups and users are evolving. Microsoft recognized the issue and is related with some SID's. The error you'll get is "An error occurred while enumerating the groups"
Therefore, I've wrote a small function to replace GetAuthorizationGroups() with better performance and error-safe. It does only 1 LDAP call with a query using indexed fields. It can be easily extended if you need more properties than only the group names ("cn" property).
// Usage: GetAdGroupsForUser2("domain\user") or GetAdGroupsForUser2("user","domain")
public static List<string> GetAdGroupsForUser2(string userName, string domainName = null)
{
var result = new List<string>();
if (userName.Contains('\\') || userName.Contains('/'))
{
domainName = userName.Split(new char[] { '\\', '/' })[0];
userName = userName.Split(new char[] { '\\', '/' })[1];
}
using (PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, domainName))
using (UserPrincipal user = UserPrincipal.FindByIdentity(domainContext, userName))
using (var searcher = new DirectorySearcher(new DirectoryEntry("LDAP://" + domainContext.Name)))
{
searcher.Filter = String.Format("(&(objectCategory=group)(member={0}))", user.DistinguishedName);
searcher.SearchScope = SearchScope.Subtree;
searcher.PropertiesToLoad.Add("cn");
foreach (SearchResult entry in searcher.FindAll())
if (entry.Properties.Contains("cn"))
result.Add(entry.Properties["cn"][0].ToString());
}
return result;
}
Within the AD every user has a property memberOf. This contains a list of all groups he belongs to.
Here is a little code example:
// (replace "part_of_user_name" with some partial user name existing in your AD)
var userNameContains = "part_of_user_name";
var identity = WindowsIdentity.GetCurrent().User;
var allDomains = Forest.GetCurrentForest().Domains.Cast<Domain>();
var allSearcher = allDomains.Select(domain =>
{
var searcher = new DirectorySearcher(new DirectoryEntry("LDAP://" + domain.Name));
// Apply some filter to focus on only some specfic objects
searcher.Filter = String.Format("(&(&(objectCategory=person)(objectClass=user)(name=*{0}*)))", userNameContains);
return searcher;
});
var directoryEntriesFound = allSearcher
.SelectMany(searcher => searcher.FindAll()
.Cast<SearchResult>()
.Select(result => result.GetDirectoryEntry()));
var memberOf = directoryEntriesFound.Select(entry =>
{
using (entry)
{
return new
{
Name = entry.Name,
GroupName = ((object[])entry.Properties["MemberOf"].Value).Select(obj => obj.ToString())
};
}
});
foreach (var item in memberOf)
{
Debug.Print("Name = " + item.Name);
Debug.Print("Member of:");
foreach (var groupName in item.GroupName)
{
Debug.Print(" " + groupName);
}
Debug.Print(String.Empty);
}
}
My solution:
UserPrincipal user = UserPrincipal.FindByIdentity(new PrincipalContext(ContextType.Domain, myDomain), IdentityType.SamAccountName, myUser);
List<string> UserADGroups = new List<string>();
foreach (GroupPrincipal group in user.GetGroups())
{
UserADGroups.Add(group.ToString());
}
In my case the only way I could keep using GetGroups() without any expcetion was adding the user (USER_WITH_PERMISSION) to the group which has permission to read the AD (Active Directory). It's extremely essential to construct the PrincipalContext passing this user and password.
var pc = new PrincipalContext(ContextType.Domain, domain, "USER_WITH_PERMISSION", "PASS");
var user = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, userName);
var groups = user.GetGroups();
Steps you may follow inside Active Directory to get it working:
Into Active Directory create a group (or take one) and under secutiry tab add "Windows Authorization Access Group"
Click on "Advanced" button
Select "Windows Authorization Access Group" and click on "View"
Check "Read tokenGroupsGlobalAndUniversal"
Locate the desired user and add to the group you created (taken) from the first step
The answer depends on what kind of groups you want to retrieve. The System.DirectoryServices.AccountManagement namespace provides two group retrieval methods:
GetGroups - Returns a collection of group objects that specify the groups of which the current principal is a member.
This overloaded method only returns the groups of which the principal is directly a member; no recursive searches are performed.
GetAuthorizationGroups - Returns a collection of principal objects that contains all the authorization groups of which this user is a member. This function only returns groups that are security groups; distribution groups are not returned.
This method searches all groups recursively and returns the groups in which the user is a member. The returned set may also include additional groups that system would consider the user a member of for authorization purposes.
So GetGroups gets all groups of which the user is a direct member, and GetAuthorizationGroups gets all authorization groups of which the user is a direct or indirect member.
Despite the way they are named, one is not a subset of the other. There may be groups returned by GetGroups not returned by GetAuthorizationGroups, and vice versa.
Here's a usage example:
PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, "MyDomain", "OU=AllUsers,DC=MyDomain,DC=Local");
UserPrincipal inputUser = new UserPrincipal(domainContext);
inputUser.SamAccountName = "bsmith";
PrincipalSearcher adSearcher = new PrincipalSearcher(inputUser);
inputUser = (UserPrincipal)adSearcher.FindAll().ElementAt(0);
var userGroups = inputUser.GetGroups();
This works for me
public string[] GetGroupNames(string domainName, string userName)
{
List<string> result = new List<string>();
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, domainName))
{
using (PrincipalSearchResult<Principal> src = UserPrincipal.FindByIdentity(principalContext, userName).GetGroups())
{
src.ToList().ForEach(sr => result.Add(sr.SamAccountName));
}
}
return result.ToArray();
}
In case Translate works locally but not remotly e.i group.Translate(typeof(NTAccount)
If you want to have the application code executes using the LOGGED IN USER identity, then enable impersonation. Impersonation can be enabled thru IIS or by adding the following element in the web.config.
<system.web>
<identity impersonate="true"/>
If impersonation is enabled, the application executes using the permissions found in your user account. So if the logged in user has access, to a specific network resource, only then will he be able to access that resource thru the application.
Thank PRAGIM tech for this information from his diligent video
Windows authentication in asp.net Part 87:
https://www.youtube.com/watch?v=zftmaZ3ySMc
But impersonation creates a lot of overhead on the server
The best solution to allow users of certain network groups is to deny anonymous in the web config
<authorization><deny users="?"/><authentication mode="Windows"/>
and in your code behind, preferably in the global.asax, use the HttpContext.Current.User.IsInRole :
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
If HttpContext.Current.User.IsInRole("TheDomain\TheGroup") Then
//code to do when user is in group
End If
NOTE: The Group must be written with a backslash \ i.e. "TheDomain\TheGroup"
This is quick and dirty but someone may find it helpful. You will need to add the reference to System.DirectoryServices.AccountManagement for this to work. This is just for getting user roles but can be expanded to include other things if needed.
using System.DirectoryServices.AccountManagement;
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "DaomainName");
UserPrincipal u = UserPrincipal.FindByIdentity(ctx, "Username");
List<UserRole> UserRoles = u.GetGroups().Select(x => new UserRole { Role = x.Name }).ToList();
public partial class UserRole
{
public string Role { get; set; }
}

Categories