How do I clone Project Online settings (O365) from one site to another?
-
12-01-2021 - |
Вопрос
I have a customer who has a fully functioning PWA site at one URL which is already on SharePoint Online, and wants a ‘playground’ for testing.
We are about to begin a full-scale migration of our on-prem SharePoint 2013 environment to Sharepoint Online. I have used a 3rd party tool to migrate all the PWA Project sites, which works just fine for moving the content, but the customer wants a full clone.
- Is there a way to copy the PWA settings programmatically (i.e.: CSOM, JSOM, powershell)?
- Since I’m a SharePoint guy and not a Project server specialist, I am curious as to where the PWA settings are stored (e.g.: the project sites’ property bags?)?
Thanks in advance!
Решение
I previously wrote an add-in on the SharePoint store called ConfigTool to do this, however the CSOM/JSOM API was limited in a number of ways that made a full-fidelity configuration copy impossible. Specifically around objects such as custom fields with lookup tables and workflow objects (PDP's, stages / phases, etc). I have since discontinued the add-in and removed it from the app store, for this reason.
So my suggestion if you want a complete copy is to use the program Fluent Pro https://fluentpro.com/fluentbooks-for-project-online/ which will do what you want (AFAIK it uses a combination of CSOM and the old PSI).
If however you have a lot of time on your hands and would like to implement something yourself, here's a quick JSON function I pulled from some old code to get you started (it is incomplete - no guarantees).
This will create a custom field from properties previously loaded from another system via the _api/ProjectServer endpoint.
// Create a CF with the contained properties of the given entity type
function createCF(properties, entityType) {
var projContext = PS.ProjectContext.get_current();
var customFields = projContext.get_customFields();
var cfInfo = new PS.CustomFieldCreationInformation();
cfInfo.set_name(properties.Name);
cfInfo.set_description(properties.Description);
cfInfo.set_fieldType(properties.FieldType);
cfInfo.set_entityType(entityType);
cfInfo.set_id(properties.Id);
cfInfo.set_isEditableInVisibility(properties.IsEditableInVisibility);
cfInfo.set_isMultilineText(properties.IsMultilineText);
cfInfo.set_isRequired(properties.IsRequired);
cfInfo.set_isWorkflowControlled(properties.IsWorkflowControlled);
cfInfo.set_formula(properties.Formula);
cfInfo.set_lookupAllowMultiSelect(properties.LookupAllowMultiSelect);
cfInfo.set_lookupDefaultValue(properties.LookupDefaultValue);
if (!!properties.LookupTable) {
// Lookup table field not handled
}
var newCF = customFields.add(cfInfo);
newCF.set_rollsDownToAssignments(properties.RollsDownToAssignments);
newCF.set_rollupType(properties.RollupType);
customFields.update();
projContext.executeQueryAsync(Function.createDelegate(this, function () {
var row = getRowWithName(properties.Name, entityType);
//updateRowPublishStatus(row, "Ok", properties.ResultMsg);
$.event.trigger("customFieldCreated", { Name: properties.Name, Entity: entityType, result: true });
}), Function.createDelegate(this, function () {
var row = getRowWithName(properties.Name, entityType);
// Check if this is a Shadowed UID, if so retry with new UID
if (arguments[1].get_errorValue() === "CustomFieldInvalidUID") {
properties.Id = guid();
properties.ResultMsg = warningCFnewUID;
dataView.getItem(row).Id = properties.Id;
dataView.refresh();
grid.render();
createCF.call(this, properties, entityType);
return;
}
if (arguments[1].get_errorValue() === "CustomFieldFormulaContainsInvalidFieldReference") {
//updateRowPublishStatus(row, "Error", errorCFformulaInvField);
$.event.trigger("customFieldCreated", { Name: properties.Name, Entity: entityType, result: false, retryable: true });
return;
}
//updateRowPublishStatus(row, "Error", arguments[1].get_errorValue());
$.event.trigger("customFieldCreated", { Name: properties.Name, Entity: entityType, result: false });
}));
}