mapping/migrating sharepoint users - sharepoint

I am currently working on a sharepoint migration on a test environment, and have now come to the point where I would like to map/migrate my existing Ad groups and users from SP 2010 to SP 2013. Now when researching this I find alot of vague information but nothing very solid concerning this matter. How would I best go about this.
Let's say I have following users in a csv file representing the users on the SP 2010 environment:
c:0!.s|windows
i:0#.w|domainhere\administrator
i:0#.w|domainhere\apservice
i:0#.w|domainhere\koen
NT AUTHORITY\LOCAL SERVICE
SHAREPOINT\system
Domainhere\APService
Should I write a powershell script somehing which resembles the following
$csv = Import-CSV ".\sites-default.csv"
$web = Get-SPWeb = "https://mymachine.mydomain"
foreach($row in $csv)
{
#You could do this to format your account name if not already in the csv
$username = "Domain\" + $row.key
$web.EnsureUser($username)
$group = $web.SiteGroups |?{$_.name -eq "GROUPNAME"}
Set-SPUser -identity $username -web $web.url -group $group
}
Or are they better ways to do this kind of matter?

I have some code that works in C# using CSOM. It reads the permissions from all lists (document libraries also are lists) and the site level and writes them to an excel.
Then it reads the permissions from Excel and puts them on the new SharePoint.
The Excel uses using OfficeOpenXml;
The model
public class PermissionsToExcel
{
public string ListTitle { get; set; }
public string Gebruikersnaam { get; set; }
public string Rechten { get; set; }
public string ListUrl { get; set; }
public bool HasUniqueRoleAssignments { get; set; }
}
The code that gets the groups and their permissions from each list:
List<PermissionsToExcel> permissionsToExcelList = new List<PermissionsToExcel>();
using (ClientContext ctx = new ClientContext(#"http://yoursharepointurl.com/"))
{
Web web = ctx.Web;
ctx.Load(web, w => w.HasUniqueRoleAssignments, w => w.Url);
ctx.Load(web.RoleAssignments);
ctx.Load(web.Lists);
ctx.Load(web.Lists, lists => lists.Include(list => list.Title, list => list.DefaultViewUrl, list => list.RoleAssignments, list => list.RoleAssignments.Groups, list => list.HasUniqueRoleAssignments));
ctx.ExecuteQuery();
//Get permissions on site level
foreach (RoleAssignment webRA in web.RoleAssignments)
{
ctx.Load(webRA.Member);
ctx.Load(webRA.RoleDefinitionBindings);
ctx.ExecuteQuery();
foreach (RoleDefinition definition in webRA.RoleDefinitionBindings)
{
ctx.Load(definition);
ctx.ExecuteQuery();
permissionsToExcelList.Add(new PermissionsToExcel() { ListTitle = "", Gebruikersnaam = webRA.Member.LoginName, Rechten = definition.Name, ListUrl = web.Url, HasUniqueRoleAssignments = web.HasUniqueRoleAssignments });
}
}
//Write down each group per list and their permissions
foreach (List list in web.Lists)
{
string listUrl = list.Context.Url + list.GetWebRelativeUrl();
foreach (RoleAssignment listRA in list.RoleAssignments)
{
ctx.Load(listRA.Member);
ctx.Load(listRA.RoleDefinitionBindings);
ctx.ExecuteQuery();
foreach (RoleDefinition definition in listRA.RoleDefinitionBindings)
{
ctx.Load(definition);
ctx.ExecuteQuery();
permissionsToExcelList.Add(new PermissionsToExcel() { ListTitle = list.Title, Gebruikersnaam = listRA.Member.LoginName, Rechten = definition.Name, ListUrl = listUrl, HasUniqueRoleAssignments = list.HasUniqueRoleAssignments });
}
}
}
}
Write the permissions to Excel using EPPlus
ExcelPackage excel = new ExcelPackage();
var workSheet = excel.Workbook.Worksheets.Add("Permissions");
workSheet.Cells[1, 1].LoadFromCollection(permissionsList, true);
using (var memoryStream = new MemoryStream())
{
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.AddHeader("content-disposition", "attachment; filename=Permissions.xlsx");
excel.SaveAs(memoryStream);
memoryStream.WriteTo(Response.OutputStream);
Response.Flush();
Response.End();
}
Read the permissions from Excel
List<PermissionsToExcel> permissionslist = new List<PermissionsToExcel>();
FileInfo existingFile = new FileInfo(#"C:\path\Permissions.xlsx");
using (ExcelPackage package = new ExcelPackage(existingFile))
{
//Get the first worksheet in the workbook
ExcelWorksheet excelWorksheet = package.Workbook.Worksheets["Permissions"];
int colCount = excelWorksheet.Dimension.End.Column; //get Column Count
int rowCount = excelWorksheet.Dimension.End.Row; //get row count
for (int row = 2; row <= rowCount; row++)//Rij 1 is de titel rij, beginnen bij rij 2
{
PermissionsToExcel permission = new PermissionsToExcel
{
ListTitle = excelWorksheet.Cells[row, 1].Value.ToString().Trim(),
Gebruikersnaam = excelWorksheet.Cells[row, 2].Value.ToString().Trim(),
Rechten = excelWorksheet.Cells[row, 3].Value.ToString().Trim(),
ListUrl = excelWorksheet.Cells[row, 4].Value.ToString().Trim(),
HasUniqueRoleAssignments = excelWorksheet.Cells[row, 5].Value.ToString().Trim().ToBoolean()
};
permissionslist.Add(permission);
}
return permissionslist;
}
Put the permissions onto a new site (which has the same library setup as the old one!!
using (ClientContext ctx = new ClientContext(#"http://newSharePointSiteUrl.com/"))
{
Web web = ctx.Web;
ctx.Load(web.Lists);
ctx.Load(web.RoleDefinitions);
ctx.ExecuteQuery();
//Ophalen rollen
RoleDefinition roleTypeOwner = ctx.Web.RoleDefinitions.GetByType(RoleType.Administrator);
RoleDefinition roleTypeEditor = ctx.Web.RoleDefinitions.GetByType(RoleType.Editor);
RoleDefinition roleTypeVisitor = ctx.Web.RoleDefinitions.GetByType(RoleType.Reader);
//RoleDefinition roleTypeNone = ctx.Web.RoleDefinitions.GetByType(RoleType.None);//Werkt niet
ctx.ExecuteQuery();
//Get groups
ctx.Load(ctx.Web.SiteGroups);
ctx.Load(ctx.Web.SiteUsers);
ctx.ExecuteQuery();
foreach (PermissionsToExcel pte in permissionslist)
{
if (pte.ListTitle == "")//If listtitle is empty, it's the site permissions
{
//Get site
User user = ctx.Web.SiteUsers.GetByLoginName(pte.Gebruikersnaam);
ctx.Load(user);
ctx.ExecuteQuery();
//Check if the site had unique permissions
if (pte.HasUniqueRoleAssignments)//Site had unique permissions, break inheritance and take away the old groups
{
RoleDefinitionBindingCollection rdbc = new RoleDefinitionBindingCollection(ctx);
switch (pte.Rechten)
{
case "Read":
rdbc.Add(roleTypeVisitor);
break;
case "Edit":
rdbc.Add(roleTypeEditor);
break;
case "Full Control":
rdbc.Add(roleTypeOwner);
break;
default:
break;
}
web.BreakRoleInheritance(false, true);
web.RoleAssignments.Add(user, rdbc);
}
else//Site had no unique permissions, inherit from above
{
//TODO: do we want that?
}
}
else if (web.ListExists(pte.ListTitle))//Go over all lists
{
//Get List
List list = web.Lists.First(t => t.Title == pte.ListTitle);
//var group = ctx.Web.SiteGroups.GetByName(pte.Gebruikersnaam);
//ctx.Load(Group);
//ctx.ExecuteQuery();
User user = ctx.Web.SiteUsers.GetByLoginName(pte.Gebruikersnaam);
ctx.Load(user);
ctx.ExecuteQuery();
//Check if list had unique permissions
if (pte.HasUniqueRoleAssignments)//List had unique permissions, stop inheritance and put back groups with their permissions
{
RoleDefinitionBindingCollection rdbc = new RoleDefinitionBindingCollection(ctx);
switch (pte.Rechten)
{
case "Read":
rdbc.Add(roleTypeVisitor);
break;
case "Edit":
rdbc.Add(roleTypeEditor);
break;
case "Full Control":
rdbc.Add(roleTypeOwner);
break;
default:
break;
}
list.BreakRoleInheritance(false, true);
//list.RoleAssignments.Add(Group, rdbc);
list.RoleAssignments.Add(user, rdbc);
}
else //List had no unique permissions, inherit from above
{
list.ResetRoleInheritance();
}
}
else
{
Debug.WriteLine("LIST NOT FOUND: PROBLEM");
}
}
}

The process you would follow is:
$user = Get-SPUser -Identity "DOM\john.smith" -Web https://siteCollectionUrl
Move-SPUser -Identity $user -NewAlias "i:0#.w|IDD\142909" -IgnoreSid
This will migrate the user farm-wide. You do not need to call EnsureUser.
If you're using the User Profile Service App, make sure your user account has explicit Full Control over the UPSA to migrate users. I also provided the example above if using Windows Claims auth. On the Get-SPUser, you don't have to specify it, but on the Move-SPUser, you do.
https://social.technet.microsoft.com/Forums/en-US/2703f6de-7a79-46b8-9184-01279a845c4b/migrating-all-users-to-a-new-domain?forum=sharepointadmin

Related

Copying existing files into a server SharePoint

I'm facing of difficulties to change a files into CSV and save it into local environment. How can I achieve this? Try to look around but seem like not what I'm looking for.
I'm running on SharePoint 2010. Before this, this code only grab data from SharePoint and turn it into xlsx and update it into our web.
private static void GenerateSPGroupUsersReport() //
{
Log("Generate Sharepoint Group Users Report");
DataSet dsSecurityReport = new DataSet();
string ConnectedWebURL = ConfigurationManager.AppSettings["SPGroupUsersWebURL"];
//string[] strURL = ConnectedWebURL.Split(';');
DataTable dTblSPGroupUser = new DataTable();
dTblSPGroupUser.Columns.Add("SiteURL", typeof(string));
dTblSPGroupUser.Columns.Add("SharepointGroup", typeof(string));
dTblSPGroupUser.Columns.Add("User", typeof(string));
// Hafees add 10/22/2019
dTblSPGroupUser.Columns.Add("UserLanID", typeof(string));
dTblSPGroupUser.Columns.Add("Email", typeof(string));
SPSite site = new SPSite(ConnectedWebURL);
SPWebApplication webApp = site.WebApplication;
foreach (SPSite s in webApp.Sites)
{
SPGroupCollection groupCol = s.RootWeb.SiteGroups;
foreach (SPGroup group in groupCol)
{
// Hafees include group.Users, user.Email
foreach (SPUser user in group.Users)
{
dTblSPGroupUser.Rows.Add(s.Url, group.Name, user.Name, user.LoginName, user.Email);
}
//bool contains = dTblSPGroupUser.AsEnumerable().Any(rowC => group.Name == rowC.Field<string>("SharepointGroup"));
//if (!contains)
//{
// foreach (SPUser user in group.Users)
// {
// dTblSPGroupUser.Rows.Add(s.Url, group.Name, user.Name);
// }
//}
}
}
DataSet dsSPGroup = new DataSet();
dsSPGroup.Tables.Add(dTblSPGroupUser);
SaveIntoSPLibrary(site, dsSPGroup, "GroupUsers_" + ConnectedWebURL.Replace("http://", "").Replace("https://", "").Replace(":", "-").Trim());
Log("Generate Sharepoint Group Users Report Complete");
}
// This is where I generate the group of user report.
private static void SaveIntoSPLibrary(SPSite site, DataSet ds, string fileName)
{
string UIResourceServerRelativeWebURL = ConfigurationManager.AppSettings["UIResourceServerRelativeWebURL"];
using (SPWeb web = site.OpenWeb(UIResourceServerRelativeWebURL))
{
byte[] byteArray = GenerateExcelFile(ds);
string CustomReportLibrary = ConfigurationManager.AppSettings["CustomReportLibrary"];
string strFileName = String.Format(fileName + ".{0}.xlsx", DateTime.Today.ToString("yyyyMMdd"));
Log("Saving into SP Library. " + CustomReportLibrary + strFileName);
web.AllowUnsafeUpdates = true;
SPFile file = web.Files.Add(CustomReportLibrary + strFileName, byteArray, true);
file.Item["Year"] = DateTime.Now.ToString("yyyy");
file.Item["Month"] = string.Format("{0}. {1}", DateTime.Now.Month, DateTime.Now.ToString("MMMM"));
file.Item.Update();
file.Update();
web.AllowUnsafeUpdates = false;
}
}
// This is where the files save into xlsx and update it into SharePoint Library.
I try to do a copy of SaveIntoLibrary with abit of modification, I change to CSV files and create a new configurationManager which it will point into my local directory. But seem I'm wrong at somewhere. The files still didn't get into my local directory. Please advice.
You should export the DataTable report data to local CSV,
Check the code in this thread, this will output the csv file so you could save to your local.
var dataTable = GetData();
StringBuilder builder = new StringBuilder();
List<string> columnNames = new List<string>();
List<string> rows = new List<string>();
foreach (DataColumn column in dataTable.Columns)
{
columnNames.Add(column.ColumnName);
}
builder.Append(string.Join(",", columnNames.ToArray())).Append("\n");
foreach (DataRow row in dataTable.Rows)
{
List<string> currentRow = new List<string>();
foreach (DataColumn column in dataTable.Columns)
{
object item = row[column];
currentRow.Add(item.ToString());
}
rows.Add(string.Join(",", currentRow.ToArray()));
}
builder.Append(string.Join("\n", rows.ToArray()));
Response.Clear();
Response.ContentType = "text/csv";
Response.AddHeader("Content-Disposition", "attachment;filename=myfilename.csv");
Response.Write(builder.ToString());
Response.End();

Update/Change custom TaxonomyFieldValue in document library list in sharepoint using C# CSOM

list of documents thats contains custom taxonomy field column named subject.
Need to update subject of thousands records/documents.
Please any idea to update the taxonomy field such subject programtically using C# CSOM
Please try to use this method:
public void UpdateTaxonomyField(ClientContext ctx, List list,ListItem listItem,string fieldName,string fieldValue)
{
Field field = list.Fields.GetByInternalNameOrTitle(fieldName);
TaxonomyField txField = clientContext.CastTo<TaxonomyField>(field);
TaxonomyFieldValue termValue = new TaxonomyFieldValue();
string[] term = fieldValue.Split('|');
termValue.Label = term[0];
termValue.TermGuid = term[1];
termValue.WssId = -1;
txField.SetFieldValueByValue(listItem, termValue);
listItem.Update();
ctx.Load(listItem);
ctx.ExecuteQuery();
}
public static void UpdateListofLibraryHavingTaxonomyField()
{
string siteUrl = "http://abc:55555/sites/xyz/";
string libName = "Knowledge Repos";
string strTermGuid = "Your new/update term guid";
try
{
Dictionary<string, string> dictIdsSubjectsChange = ReadFromExcel();//Here to read all records (approx 2000 records) that we want to change
ClientContext clientContext = new ClientContext(siteUrl);
List list = clientContext.Web.Lists.GetByTitle(libName);
FieldCollection fields = list.Fields;
Field field = fields.GetByInternalNameOrTitle("Subject1");
ListItemCollection listItems = list.GetItems(CamlQuery.CreateAllItemsQuery());
clientContext.Load(listItems, items => items.Include(i => i["Subject1"], i => i["ID"]));
clientContext.Load(fields);
clientContext.Load(field);
clientContext.ExecuteQuery();
TaxonomyField txField = clientContext.CastTo<TaxonomyField>(field);
TaxonomyFieldValue termValue = null;
if (dictIdsSubjectsChange != null)
{
foreach (ListItem listItem in listItems)//Loop through all items of the document library
{
string strCurrentID = "0";
try
{
strCurrentID = listItem["ID"].ToString();
}
catch (Exception) { }
if (dictIdsSubjectsChange.ContainsKey(strCurrentID))//Checking to change ot not
{
termValue = new TaxonomyFieldValue();
termValue.Label = "Special Knowledge";
termValue.TermGuid = strTermGuid;
termValue.WssId = 246;
txField.SetFieldValueByValue(listItem, termValue);
listItem.Update();
clientContext.Load(listItem);
}
}
clientContext.ExecuteQuery();
}
}
catch (Exception ex) { }
}
}

Read All users related to RoleDefinition of ProjectSite using CSOM

I need to loop through all RoleDefinitiona of ProjectSite and get all users related to each one and add those users to a RoleDefinition of a ProjectSite in another SiteCollection
I can loop through RoleDefinitions like :
using (var src_ctx = new ClientContext(Root))
{
RoleDefinitionCollection role_definition_collection = src_ctx.Web.RoleDefinitions;
src_ctx.Load(role_definition_collection);
src_ctx.ExecuteQuery();
foreach (RoleDefinition role_definition in role_definition_collection)
{
}
}
also I can loop through all groups and read users of each group then get RoleAssignment of each user:
GroupCollection group_collection = src_ctx.Web.SiteGroups;
src_ctx.Load(group_collection);
src_ctx.ExecuteQuery();
foreach (Group group in group_collection)
{
UserCollection user_collection = group.Users;
foreach (User user in user_collection)
{
RoleAssignment role_assignment = src_ctx.Web.RoleAssignments.GetByPrincipal(user);
RoleDefinitionBindingCollection role_definition_binding_collection = role_assignment.RoleDefinitionBindings;
}
}
but how can I link between RoleDefinition and users ?
the following class used to copy users with its permissions, may help someone
public static class UserHelper
{
// static members
private static Dictionary<string, string> Levels = new Dictionary<string, string>();
private static void Prepare_Dictionary()
{
Levels["Full Control"] = "Full Control";
Levels["Design"] = "Design";
Levels["Contribute"] = "Contribute";
Levels["Read"] = "Read";
Levels["Limited Access"] = "Limited Access";
}
public static void Start_Moving_Users(string Root2010,string Root2013)
{
Prepare_Dictionary();
using (ClientContext src_ctx = new ClientContext(Root2010))
{
using (ClientContext dest_ctx = new ClientContext(Root2013))
{
dest_ctx.Web.BreakRoleInheritance(true, false);
RoleAssignmentCollection src_rac = src_ctx.Web.RoleAssignments;
src_ctx.Load(src_rac);
src_ctx.ExecuteQuery();
foreach (RoleAssignment src_ra in src_rac)
{
try
{
src_ctx.Load(src_ra.Member);
src_ctx.ExecuteQuery();
RoleDefinitionBindingCollection src_rdb = src_ra.RoleDefinitionBindings;
src_ctx.Load(src_rdb);
src_ctx.ExecuteQuery();
RoleDefinition src_rd = src_rdb[0];
src_ctx.Load(src_rd);
src_ctx.ExecuteQuery();
Principal dest_user = dest_ctx.Web.EnsureUser(src_ra.Member.LoginName);
RoleDefinition dest_rd = dest_ctx.Web.RoleDefinitions.GetByName(Levels[src_rd.Name]);
RoleDefinitionBindingCollection dest_rdb = new RoleDefinitionBindingCollection(dest_ctx);
dest_rdb.Add(dest_rd);
dest_ctx.Web.RoleAssignments.Add(dest_user, dest_rdb);
dest_ctx.ExecuteQuery();
}
catch { continue; }
}
}
}
}
}

RequestToJoinLeaveEmailSetting is not being set properly in sharepoint 2010

Using object model I am updating group. The value I am setting in 'RequestToJoinLeaveEmailSetting' field is not showing when using PowerShell command. However, when I am fetching it by the object model, it is showing the new value set to 'RequestToJoinLeaveEmailSetting' field.
Using the PowerShell commands I am able to update this field. However, from the object model I am getting the value set from itself, not that is set by PowerShell.
How, can it be in sync? Any help/idea?
Thanks in advanced.
Mohak
Here is my Code:
SPSite Site = new SPSite(siteUrl);
SPWeb spWeb = Site.OpenWeb();
SPGroup spGroup = spWeb.SiteGroups[oldname];
SPRoleCollection roles = spGroup.Roles;
if (roles != null)
{
oldRoles = new ArrayList();
foreach (SPRole role in roles)
{
oldRoles.Add(role.Name);
}
}
// here we are comparing the old and new roles to be updated and separating out
// which roles to be deleted and which is to be updated.
foreach (string role in oldRoles)
{
if (newRoles.Contains(role))
{
updatedRoles.Add(role);
}
else
{
removeRoles.Add(role);
}
}
foreach (string rolenames in newRoles)
{
if (!oldRoles.Contains(rolenames))
{
updatedRoles.Add(rolenames);
}
}
if (removeRoles != null && removeRoles.Count > 0)
{
SPRoleAssignment roleAssignment = new SPRoleAssignment(spGroup);
foreach (string str in removeRoles)
{
SPRoleDefinition role = spWeb.RoleDefinitions[str];
//SPRoleAssignment roleAssignment = new SPRoleAssignment(spGroup);
roleAssignment.RoleDefinitionBindings.Remove(role);
spWeb.RoleAssignments.Remove(roleAssignment.Member);
spWeb.Update();
}
spWeb.Update();
}
if (spGroup != null)
{
spGroup.Description = description;
spGroup.Name = name;
spGroup.OnlyAllowMembersViewMembership = viewprmsn;
spGroup.AllowMembersEditMembership = edprmsn;
spGroup.AllowRequestToJoinLeave = mbrrqst;
spGroup.AutoAcceptRequestToJoinLeave = acptrqst;
spGroup.RequestToJoinLeaveEmailSetting = emailid;
if (updatedRoles != null && updatedRoles.Count > 0)
SPRoleAssignment roleAssignment = new SPRoleAssignment(spGroup);
// SPRoleDefinition roleDefinition = spWeb.RoleDefinitions["Contribute"];
foreach (string str in updatedRoles)
{
SPRoleDefinition roleDefinition = spWeb.RoleDefinitions[str];
roleAssignment.RoleDefinitionBindings.Add(roleDefinition);
}
spWeb.RoleAssignments.Add(roleAssignment);
}
//spGroup.RequestToJoinLeaveEmailSetting = emailid;
spGroup.Update();
}
spWeb.Update();
}
catch (Exception ex)
{
SPTlogger.Error("-------------------------ERROR-------------------------");
SPTlogger.Error("Error in UpdateGroup():" + ex.Message);
throw new Exception(ex.Message);
}
finally
{
SPTlogger.Debug("<-- : UpdateGroup()");
}
}
It seems you forgot to apply changes using SPGroup.Update method when the group properties have been updated
PS example:
$web = Get-SPWeb $webUrl
$membersGroup = $web.AssociatedMemberGroup
$membersGroup.RequestToJoinLeaveEmailSetting = "admin#intranet.contoso.com"
$membersGroup.Update() #save group changes

How can I use start script change IIS Application pool pipeline mode

I used a web role.
I just wonder if I can use Start script for my project to change the pipeline mode to classic.
I can use C# code achieve that, but I prefer to use cmd, The problem I meet here is how can I get the applicationPool name by cmd?
Here is my C# code:
{
using (ServerManager serverManager = new ServerManager())
{
Site site = serverManager.Sites[RoleEnvironment.CurrentRoleInstance.Id + "_Web"];
Configuration config = serverManager.GetApplicationHostConfiguration();
string AppPoolName = site.Applications[0].ApplicationPoolName;
ConfigurationSection applicationPoolsSection = config.GetSection("system.applicationHost/applicationPools");
ConfigurationElementCollection applicationPoolsCollection = applicationPoolsSection.GetCollection();
ConfigurationElement addElement = FindElement(applicationPoolsCollection, "add", "name", AppPoolName);
if (addElement == null) throw new InvalidOperationException("Element not found!");
addElement["managedPipelineMode"] = #"Classic";
serverManager.CommitChanges();
return base.OnStart();
}
}
private static ConfigurationElement FindElement(ConfigurationElementCollection collection, string elementTagName, params string[] keyValues)
{
foreach (ConfigurationElement element in collection)
{
if (String.Equals(element.ElementTagName, elementTagName, StringComparison.OrdinalIgnoreCase))
{
bool matches = true;
for (int i = 0; i < keyValues.Length; i += 2)
{
object o = element.GetAttributeValue(keyValues[i]);
string value = null;
if (o != null)
{
value = o.ToString();
}
if (!String.Equals(value, keyValues[i + 1], StringComparison.OrdinalIgnoreCase))
{
matches = false;
break;
}
}
if (matches)
{
return element;
}
}
}
return null;
}
}
So How can I do that ?
If the pool name is the only problem you are facing, try using appcmd for listing all available pools:
appcmd.exe list apppool /text:*
This should give you all apppools available on IIS with the app names (provided you have enough rights).

Resources