Application to copy a SharePoint Group - c#

As per this entry: Cloning a sharepoint rolegroup I'm trying to create a console application to copy a SharePoint group, including its permissions.
Based on the answer from Tjassens I've reached the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
namespace REGroupCopy
{
class Program
{
static void Main(string[] args)
{
using (SPSite spSite = new SPSite("http://dev"))
{
using (SPWeb spWeb = spSite.RootWeb)
{
// first we find the group that we want to clone
SPGroup group = spWeb.Groups["Test Group"];
// then we use this retreived group to get the roleassignments on the SPWeb object
SPRoleAssignment ass = spWeb.RoleAssignments.GetAssignmentByPrincipal(group);
string groupName = "Test Group 2"; // group to create
string groupDescription = "Group created by REGroupCopy";
string user = "michael";
spWeb.SiteGroups.Add(groupName, user, user, groupDescription);
SPGroup newGroup = spWeb.SiteGroups[groupName];
SPRoleAssignment roleAssignment = new SPRoleAssignment(newGroup);
//add role to web
spWeb.RoleAssignments.Add(roleAssignment);
spWeb.Update();
}
}
}
}
}
Unfortunately I don't think I'm understanding everything correctly. Specifically, I think these lines are incorrect, but I'm unsure what they should be:
string groupName = "Test Group 2"; // group to create
string groupDescription = "Group created by REGroupCopy";
string user = "michael";
spWeb.SiteGroups.Add(groupName, user, user, groupDescription);
I don't necessarily need somebody to come along and fix this for me (after all ,this is a learning exercise). Instead, could you please help me to understand where my thought process is falling down and what I need to learn to rectify this?

You have found the correct problem with your code. When you call the following method:
spWeb.SiteGroups.Add(groupName, user, user, groupDescription);
you forgot that the user should not be a string but an actual SPUser object. If you get the SPUser object you should be able to add the new group to the SPWeb/SPSite.
you can get the user object by using for instance:
SPUser spUser = spWeb.EnsureUser(loginName);

Add method :
First Param : The new group name
Second param : The owner (SPUser object)
Third param : The default user for the group (SPMember object).
Fourth param : The new group description
From Site admin New group
first param is like Name TextBox
second param and third param is like Group owner people picker
fourth param is like About me RichTextBox

Related

How to get Unique Display name for VSTS/TFS identity?

I am a writing a .Net application using the VSTS/TFS Rest .Net libraries and in one place I need to update workitems' System.AssignedTo field values and while I do want to adhere to the new(ish), unique displayname rules for identity work item fields, I have a hard time finding a method to get the Unique display name(s) for given identities.
The old / client object model does have an explicit helper method to get these unique names, but I have not found any rest endpoint nor client api method that would provide the same.
So I am wondering, given a list of identities, how do I get their corresponding unique display names which I can use to unambiguously set identity work item fields?
String collectionUri = "http://collectionurl/";
VssCredentials creds = new VssClientCredentials();
creds.Storage = new VssClientCredentialStorage();
VssConnection connection = new VssConnection(new Uri(collectionUri), creds);
TeamHttpClient thc = connection.GetClient<TeamHttpClient>();
List<IdentityRef> irs = thc.GetTeamMembersAsync("ProjectName","TeamName").Result;
foreach (IdentityRef ir in irs)
{
Console.WriteLine(ir.UniqueName);
Console.WriteLine(ir.DisplayName);
}
You could try the code below to get unique name:
using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
TfsConfigurationServer tcs = new TfsConfigurationServer(new Uri("http://tfsserver:8080/tfs"));
IIdentityManagementService ims = tcs.GetService<IIdentityManagementService>();
TeamFoundationIdentity tfi = ims.ReadIdentity(IdentitySearchFactor.AccountName, "[TEAM FOUNDATION]\\Team Foundation Valid Users", MembershipQuery.Expanded, ReadIdentityOptions.None);
TeamFoundationIdentity[] ids = ims.ReadIdentities(tfi.Members, MembershipQuery.None, ReadIdentityOptions.None);
foreach (TeamFoundationIdentity id in ids)
{
if (id.Descriptor.IdentityType == "System.Security.Principal.WindowsIdentity")
{
Console.WriteLine(id.DisplayName);
Console.WriteLine(id.UniqueName);
}
}
Console.ReadLine();
}
}
}
foreach (var workItem in workItems)
{
if (workItem.Fields.ContainsKey("System.AssignedTo"))
{
var person = (IdentityRef)workItem.Fields["System.AssignedTo"];
string codereview_reviewer = person.DisplayName;
Console.WriteLine(codereview_reviewer);
}
}

Autocomplete Text Box With Active Directory Users

Hi there I am trying to create a textbox that when a user types into it they get a list of users with the specific name:
Example: If I started to type Jane.Doe, and I had only typed in Ja a list would come up with users from the Active Directory who start with Ja. I need to figure out how I can get the Users to a list each time a user types. I pretty much have the ajax side done. Its just getting the list of users updated each time.
My current idea:
[HttpPost]
public ActionResult RemoteData(string query)
{
List<string> lstADUsers = new List<string>();
using (var context = new PrincipalContext(ContextType.Domain, null, "LDAPPATH"))
{
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
foreach (var result in searcher.FindAll())
{
DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry;
string usersWithName;
if (!String.IsNullOrEmpty((String)de.Properties["samaccountname"].Value))
{
usersWithName = de.Properties["samaccountname"].Value.ToString();
lstADUsers.Add(usersWithName);
}
}
}
}
List<string> listData = null;
if (!string.IsNullOrEmpty(query))
{
listData = lstADUsers.Where(q => q.ToLower().StartsWith(query.ToLower())).ToList();
}
return Json(new { Data = listData });
}
So this allows us to get EVERY user in the Active Directory but I don't want this because the issue at hand gets that there are too many users and the search takes FOREVER to load this before it even displays the list of names. I only want to be able to take a parameter and only search for user that starts with that. How would I go about doing this?
You need to populate the Name property of UserPrincipal with a wildcard to limit the result set:
// assume 'query' is 'Ja'
UserPrincipal user = new UserPrincipal(context);
user.Name = query + "*"; // builds 'Ja*', which finds names starting with 'Ja'
using (var searcher = new PrincipalSearcher(user))
// ...

Add member to AD group from a trusted domain

I have two domains, in a trusted relationship, that I'm trying to manage from a C# web application. To do that, I have to impersonate two different technical users, but that works good, so I will not emphasize that part of the code.
To build proper and easy to manage ACLs for the file system, I must
Create a group in domainA (OK!)
Find a user in domainB (OK!)
Add the user to the group (FAILS when committing changes, error message: There is no such object on the server. (Exception from HRESULT: 0x80072030))
If I'm adding a user from the same domain, the code works perfectly, so I believe I'm only missing a small partial info here. I used this document as a reference and saw this question as well (and a few more citing this error message) but neither of them helped.
Code (try-catch block removed to make it simpler)
// de is a DirectoryEntry object of the AD group, received by the method as a parameter
// first impersonation to search in domainB
// works all right
if (impersonator.impersonateUser("techUser1", "domainB", "pass")) {
DirectoryEntry dom = new DirectoryEntry("LDAP://domainB.company.com/OU=MyOU,DC=domainB,DC=company,DC=com", "techUser1", "pass");
de.Invoke("Add", new object[] { "LDAP://domainB.company.com/CN=theUserIWantToAdd,OU=MyOU,DC=domainB,DC=company,DC=com" });
// de.Invoke("Add", new object[] { "LDAP://domainA.company.com/CN=anotherUserFromDomainA,OU=AnotherOU,DC=domainB,DC=company,DC=com" });
impersonator.undoImpersonation();
}
// second impersonation because the group (de) is in domainA
// and techUser2 has account operator privileges there
if (impersonator.impersonateUser("techUser2", "domainA", "pass"))
{
de.CommitChanges();
impersonator.undoImpersonation();
return true;
}
else
{
// second impersonation was unsuccessful, so return an empty object
return false;
}
Line 6 works, if I debug it or force the properties to be written to HttpResponse, it is clearly there. So the LDAP queries seem to be OK.
Also, if I comment out line 6 and uncomment 7, so basically I add a user from the same domain, the whole thing works miraculously. With domainB, I'm stuck. Any good piece of advice?
Following your code, I see that you're getting de as a parameter, which is in Domain A. Then you're creating DirectoryEntry object dom, which is getting impersonated, but never getting used. However, you're trying to add an object from Domain B to de directly using LDAP. This line:
de.Invoke("Add", new object[{"LDAP://domainB.company.com/CN=theUserIWantToAdd,OU=MyOU,DC=domainB,DC=company,DC=com" });
is not getting impersonated.
Assuming your impersonation works correctly, use dom object which is already impersonated with DirectorySearcher to find the user in Domain B and then add the user object from Domain B to de.
...
using (DirectoryEntry dom = new DirectoryEntry("LDAP://domainB.company.com/OU=MyOU,DC=domainB,DC=company,DC=com", "techUser1", "pass"))
{
using (DirectorySearcher searcher = new DirectorySearcher(dom))
{
searcher.Filter = "(&(objectClass=user)(CN=theUserIWantToAdd))";
SearchResult result = searcher.FindOne();
de.Invoke("Add", new object[] { result.Path });
}
}
...
UDPATE
This example will show you how to get user SID from one domain, search group from another domain and add user to group using SID.
//GET THE USER FROM DOMAIN B
using (UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(domainContext, UPN))
{
if (userPrincipal != null)
{
//FIND THE GROUP IN DOMAIN A
using (GroupPrincipal groupPrincipal = GroupPrincipal.FindByIdentity(domainContext, groupName))
{
if (groupPrincipal != null)
{
//CHECK TO MAKE SURE USER IS NOT IN THAT GROUP
if (!userPrincipal.IsMemberOf(groupPrincipal))
{
string userSid = string.Format("<SID={0}>", userPrincipal.SID.ToString());
DirectoryEntry groupDirectoryEntry = (DirectoryEntry)groupPrincipal.GetUnderlyingObject();
groupDirectoryEntry.Properties["member"].Add(userSid);
groupDirectoryEntry.CommitChanges();
}
}
}
}
}
Please note that I skipped all the impersonation in the above code.
What finally worked was using principals as Burzum suggested. The original code samples you can see in the MSDN article linked in the question did not work here. So the Principal-based approach is a must nut not enough. You need one more line before committing changes of the new group:
group.Properties["groupType"].Value = (-2147483644);
The default was 0x8000000 and I had to change it to 0x80000004 to enable it to accept FSPs from another domain.
So now the group exists, it has members, it is added to the ACL of the folder.

Add contact to distribution list programmatically

I am really stuck in this issue and searching didn't yield me a lot. Most answers I found either get Contacts not add them or use LDAP.
The best I've been able to do is display the window where you add people to the distribution list but I am not able to do that part programmatically
Here is the my best attempt:
Microsoft.Office.Interop.Outlook.Application oApp = new Microsoft.Office.Interop.Outlook.Application();
NameSpace oNS = oApp.GetNamespace("MAPI");
//Get Global Address List.
AddressLists oDLs = oNS.AddressLists;
AddressList oGal = oDLs["Global Address List"];
AddressEntries oEntries = oGal.AddressEntries;
AddressEntry oDL = oEntries["MyDistributionList"];
//Get Specific Person
SelectNamesDialog snd = oApp.Session.GetSelectNamesDialog();
snd.NumberOfRecipientSelectors = OlRecipientSelectors.olShowTo;
snd.ToLabel = "D/L";
snd.ShowOnlyInitialAddressList = true;
snd.AllowMultipleSelection = false;
//snd.Display();
AddressEntry addrEntry = oDL;
if (addrEntry.AddressEntryUserType == Microsoft.Office.Interop.Outlook.OlAddressEntryUserType.olExchangeDistributionListAddressEntry)
{
ExchangeDistributionList exchDL = addrEntry.GetExchangeDistributionList();
AddressEntries addrEntries = exchDL.GetExchangeDistributionListMembers();
string name = "John Doe";
string address = "John.Doe#MyCompany.com";
exchDL.GetExchangeDistributionListMembers().Add(OlAddressEntryUserType.olExchangeUserAddressEntry.ToString(), name, address);
exchDL.Update(Missing.Value);
}
Using this i can access the Distribution List but I get "The bookmark is not valid" exception on the
exchDL.GetExchangeDistributionListMembers().Add(OlAddressEntryUserType.olExchangeUserAddressEntry.ToString(), name, address);
line.
I have access on said list.
EDIT:
The thing is that when you use the Outlook API, you use its functionality as a user, not as an admin. More than that, you can only do things that you can do through Outlook UI.
Outlook doesn't allow you to modify distribution lists, so you won't be able to do it using the outlook API.
There are 2 possible ways to do it:
Use the NetApi functions NetGroupAddUser or NetLocalGroupAddMembers, depending on whether the group is a local or global group. This will require importing those functions with P/Invoke and won't work on universal groups.
2. Use LDAP to find the group you need, and add the users you want to it. This can be done using the System.DirectoryServices namespace like this:
using(DirectoryEntry root = new DirectoryEntry("LDAP://<host>/<DC root DN>"))
using(DirectorySearcher searcher = new DirectorySearcher(root))
{
searcher.Filter = "(&(objName=MyDistributionList))";
using(DirectoryEntry group = searcher.findOne())
{
searcher.Filter = "(&(objName=MyUserName))";
using(DirectoryEntry user = searcher.findOne())
{
group.Invoke("Add", user.Path);
}
}
}
These just wrap the old COM ADSI interfaces, that's why I use group.Invoke(). It takes a bit more practice, but is much more powerful than the NetApi functions.

List users in active directory domain AND subdomain

We have an AD with users in "mydomain.com" and users in "child.mydomain.com". When We try to list them, we can only find the "mydomain.com"'s users and groups, but we also need those from the child domain. How can I achieve this using C# ? Please take a look to my sample code :
context = new PrincipalContext(ContextType.Domain);
//...
var filter = new GroupPrincipal(context);
filter.IsSecurityGroup = true;
using(var searcher = new PrincipalSearcher(filter)
using(var results = searcher.FindAll())
{
foreach(GroupPrincipal group in results)
{
string path = "LDAP://rootDSE";
DirectoryEntry searchRoot = new DirectoryEntry(path);
string configNC = searchRoot.Properties["configurationNamingContext"].Value.ToString();
DirectoryEntry configSearchRoot = new DirectoryEntry("LDAP://" + configNC);
DirectorySearcher configSearch = new DirectorySearcher(configSearchRoot);
configSearch.Filter("(NETBIOSName=*)");
configSearch.PropertiesToLoad.Add("dnsroot");
configSearch.PropertiesToLoad.Add("ncname");
configSearch.PropertiesToLoad.Add("NETBIOSName");
SearchResultCollection forestPartitionList = configSearch.FindAll();
List<Tuple<string,string>> netbiosNameList = new List<Tuple<string,string>>(forestPartitionList.Count);
foreach(SearchResult domainPartition in forestPartitionList)
{
string ncname = domainPartition.Properties["ncname"][0].ToString();
string netBIOSName = domainPartition.Properties["NETBIOSName"][0].ToString();
netbiosNameList.Add(Tuple.Create(ncname, netBIOSName));
}
//...
//Find group members
using (var principal = GroupPrincipal.FindByIdentity(context, IdentityType.DistinguishedName, group.DistinguishedName))
using (var members = principal.GetMembers(true))
using (var enumerator = members.GetEnumerator())
{
//...
}
}
}
The code is not exactly written this way, I just want to show you the main calls that are made to query the AD. We can list the parent domain groups and users but not the child domain ones. If I change the initialization of my "context" variable passing the child domain IP and user/password, I can list the groups and users in it. But we want to be able to do so while being in the parent domain.
I hope you can help me. Thanks a lot!
You can query the global catalog.
It contains a read-only, searchable, partial representation of every object in every domain in a multidomain Active Directory forest.
The GC operates on port 3268 ( standard ldap ) and 3269 ( SSL ldap ). Simply connect to any of your domain controllers on one of the above two ports and your search will be automatically directed to the GC server.
To perform any modifications, though, you will have to send such request to a domain controller for that particular domain the object belongs to.

Categories

Resources