Enum all quicklaunch and replace url

Nel codice seguente ho enumerato tutti gli oggetti SPWeb partendo da un site e per ciascuno ho fatto accesso all'oggetto Navigation per sostituire delle URL assolute provenienti da una migrazione, portandole in URL relative:

Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue

$Web = 'http://mywebapp'
$FindString = 'https://sourceurltofind' 
$ReplaceString = ''

$SPSite = Get-SPSite $Web
$SPWeb = Get-SPWeb $Web

$SPSite.AllWebs | ForEach-Object {
    write-host "Work with <"$_.Title"> <"$_.ServerRelativeUrl">"

    $_.Navigation.QuickLaunch | ForEach-Object {

        if($_.Url -match $FindString){
            $linkUrl = $_.Url
            Write-Host "Updating $linkUrl with new URL"
            $_.Url = $_.Url.Replace($FindString,$ReplaceString)
            $_.Update()
        }
        $_.Children | ForEach-Object {
            if($_.Url -match $FindString){
                $linkUrl = $_.Url
                Write-Host "Updating $linkUrl with new URL"
                $_.Url = $_.Url.Replace($FindString,$ReplaceString)
                $_.Update()
            }
        }
    }
    $_.Dispose()

}

SPDocumentLibrary a supporto di item non visibili

Poco tempo fa ho trovato una lista con circa 300K di item, più nello specifico pagine, (non spiego per quale motivo c'erano tutti quegli elementi) di cui circa il 90% erano in Check Out e non visualizzabili nella lista, nè con l'interfaccia di SharePoint nè con l'utilizzo dell'object model (SPList), la cosa buffa è che ero Site Collection Administrator e che la count degli item mi forniva un valore diverso rispetto a quanto invece riuscivo a visualizzare. A questo punto avevo la necessità, sia per riorganizzare la lista che per riportare le performance al top, di cancellare tutti quegli item.

Come dicevo prima l'oggetto SPList era coerente solo con la count ma non con l'accesso ai dati quindi, navigando sulla rete, ho trovato un altro modo per riportare alla normalità la lista.

L'oggetto SPDocumentLibrary consente di accedere alla lista, diciamo a basso livello, vediamo nello specifico caso il codice da me utilizzato:

SPSecurity.RunWithElevatedPrivileges(delegate() {
      using (SPSite site = new SPSite(URL)) {
           using (SPWeb web = site.OpenWeb()) {
                 SPDocumentLibrary docLib = (SPDocumentLibrary)web.Lists["Pagine"];

                 StringBuilder sbDelete = new StringBuilder();
                 sbDelete.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>               <Batch>");  
                 string command = "<Method><SetList Scope=\"Request\">" + docLib.ID + "</SetList><SetVar Name=\"ID\">{0}</SetVar><SetVar Name=\"Cmd\">Delete</SetVar><SetVar Name=\"owsfileref\">{1}</SetVar></Method>";

                  foreach (var item in docLib.CheckedOutFiles) {
                         item.TakeOverCheckOut();
                         sbDelete.Append(string.Format(command, item.ListItemId.ToString(), "/" + item.Url));
                  }

                  sbDelete.Append("</Batch>");
                  String results = web.ProcessBatchData(sbDelete.ToString());
                  docLib.Update();
           }
}}

User Profile vs User Information List + Performance Test

Analizzando un aspetto interessante, sempre in ambito di sviluppi custom, inerente l'accesso alle informazioni personali degli utenti (DisplayName, Account Name, Picture, ecc.) ho messo a confronto l'uso del servizio User Profile e l'uso dell'oggetto SiteUserInfoList, vediamo il codice:

Utilizzo del servizio User Profile

 

SPServiceContext context = SPServiceContext.GetContext(site);
UserProfileManager objUserProfileManager = new UserProfileManager(context);
UserProfile profile = objUserProfileManager.GetUserProfile(LoginName);

string Title1 = profile.DisplayName;

 

Utilizzo dell'oggetto SiteUserInfoList

 

SPUser userinweb = web.EnsureUser(LoginName);
string Query = "<Where><Eq><FieldRef Name='ID' /><Value Type='Counter'>" + userinweb.ID + "</Value></Eq></Where>";
string ViewFields = "<FieldRef Name='Title' Nullable='TRUE' /><FieldRef Name='Picture' Nullable='TRUE' />";

SPQuery _query = new SPQuery();
_query.ViewFields = ViewFields;
_query.Query = Query;

SPListItem userItem = web.SiteUserInfoList.GetItems(_query)[0];

string Title2 = userItem["Title"].ToString();

 

Come si può notare, nel secondo caso, ho preferito utilizzare una CAML Query e quindi prendere il risulato con l'oggetto GetItems invece di usare il GetItemsById(userinweb.ID) perchè a livello di performance è più vantaggioso (strano ma vero) il primo metodo invece del secondo.

Ho chiamato questi due metodi, con un ciclo, per 100 volte e il risultato è stato: vince 7 a 0 l'oggetto SiteUserInfoList, a titolo di esempio i tempi che ho preso:

In questo link che ho trovato sulla rete si possono vedere tutti i campi che potranno essere interrogati per il SiteUserInfoList: 

http://social.msdn.microsoft.com/Forums/en-US/sharepointdevelopment/thread/1c90abd0-54d7-482b-b16c-4c323bfbbd2c/

Il test l'ho eseguito in locale con un Notebook con processore i7, SSD e 8Gb di Ram, ma l'ho provato anche su una farm composta da 30 server (tipo NASA) e il risultato è stato che per la SiteUserInfoList la differenza tra il tempo finale e quello iniziale è pari a 0, mentre per il servizio User Profile rimane in termine di millesimi un leggero attimo di latenza.

Social Comments e Security Trimming

Eccomi qui a scrivere un bel post sui Commenti di SharePoint, che mi ha fatto tribolare per un pò ma che alla fine sono riuscito a svangarla (direi come al solito :P).

In un ambiente enterprise dove la security trimming è attivata potremmo avere qualche difficoltà con alcuni oggetti "social" di SharePoint, in particolare io mi sono scontrato con il metodo GetComments dell'oggetto SocialCommentManager, perchè? Perchè avevo la necessità di far visualizzare a livello pubblico i commenti inseriti su una determinata Uri dagli utenti della intranet che sto sviluppando. Il risultato era che sul mio ambiente di sviluppo, come al solito, tutto fila liscio, mentre una volta fatto il deploy dal cliente...succedeva che ogni utente vedeva i propri commenti :( azz!

Il seguente snippet riporta il modo con cui richiamavo il metodo GetComments:

 

SPWeb web = SPContext.Current.Web;
SPServiceContext context = SPServiceContext.Current;

Uri uri = new Uri(web.Url + pageUrl);

SocialCommentManager cm = new SocialCommentManager(context);
SocialComment[] commenti = cm.GetComments(uri);

 

Ma non finisce qui, dopo che ho certificato che il codice era giusto ma il risultato atteso non era esatto, provo con il web service di SharePoint e quindi altro snippet:

 

SocialDataService sds = new SocialDataService();
            Microsoft.Office.Server.SocialData.SocialDataService.SocialCommentDetail[] comments = sds.GetCommentsOnUrl(string.Concat(SPContext.Current.Site.Url, pageUrl), null, null, null);

 

Niente, sempre lo stesso risultato :(

Bene, disassembliamo e vediamo che cosa fanno i metodi e faccio una scoperta che mi lascia stupefatto. C'è un bel metodo INTERNAL ..... GetComments che accetta un parametro bool needSecurityTrim e ora?

 

Sia lodata la Reflection, con l'ultimo snippet potrete avere la soluzione ai vostri problemi, senza dover ricorrere a disabilitare il Security Trimming via PowerShell:

 

var type = typeof(SocialCommentManager);
            var methods = type.GetMethods(System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            var method = methods.FirstOrDefault(m => m.ToString() == "Microsoft.Office.Server.SocialData.SocialComment[] GetComments(System.Uri, Int32, Int32, System.DateTime, Boolean)");
            if (method == null) throw new MissingMethodException("Social GetComment method not found.");
            var comments = method.Invoke(cm, new object[] { uri, 10000, 0, DateTime.MinValue, false }) as SocialComment[];

 

SPQuery: count items

Per recuperare il numero degli items di una lists per una determinata Query, eventualmente creata a run time, la sintassi base che uno scriverebbe è la seguente:

 

int ItemCount;
SPQuery query = new SPQuery();
query.Query = view.Query;
ItemCount = list.GetItems(query).Count;

 

da questo snippet ho omesso il RowLimit perchè altrimenti non avrei una Count valida, supponendo di creare un paginatore. 

Ma qui ho riscontrato un enorme problema, i tempi di risposta, perchè il GetItems anche se alla fine richiediamo il Count comunque restituisce tutti gli items e poi ritorna il totale, ho notato che già con 3000 items i tempi di attesa si aggirano sui 10/14 secondi (un botto!!!).

La soluzione è banalissima, comunque siamo costretti a fare il GetItems, ma considerato che abbiamo necessità solo di una Count cerchiamo di minimizzare il tutto e quindi nella mia query specificherò che i ViewFields dovranno essere solamente 1 cioè l'ID (numerico), quindi insieme allo snippet di prima aggiungeremo anche:

 

query.ViewFields = "<FieldRef Name='ID' />";

 

Kid's stuff

 

Error occurred in deployment step 'Activate Features': A timeout has occurred while invoking commands in SharePoint host process.

Bene, dopo una giornata intera a cercare di effetture un deploy della solution di SharePoint tramite Visual Studio ma con scarsissimi risultati (vedi titolo del POST) ho trovato nella rete la seguente procedura:

Apri il RegEdit.exe

vai nel folder HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0\SharePointTools

aggiungere se non esiste la chiave 

ChannelOperationTimeout

REG_DWORD

Decimal -> 800 (il valore è in secondi)

Kid's stuff ;)

Taxonomy & Custom Sort Order

Veramente banale gestire la visualizzazione dei termini di un Term Set di SharePoint, ma sono veramente insoddisfatto per come ho dovuto reperire il dato (sembra come se gli sviluppatori Microsoft andavano di corsa....), comunque, nel Term Store Management Tool della Central Administration dopo aver selezionato il gruppo e il Term Set sul pannello di destra compariranno i tabs come da figura:

Cliccando sul tab CUSTOM SORT si potranno assegnare le priorità di visualizzazione (assolutamente no!) desiderata per il vostro Term Set:

Perchè prima ho scritto e sottolineato "assolutamente no!"?

Perchè per reperire la lista dei termini con object model si utilizzerà questo codice:

TaxonomySession session = new TaxonomySession(site);
TermStore termstore = session.TermStores[TaxonomyUtils.TERM_STORE_NAME];
group = termstore.Groups.GetByName(TaxonomyUtils.TERM_GROUP);
termSets = group.TermSets[termSetName];

if (!string.IsNullOrEmpty(termSets.CustomSortOrder))
{
           string[] taxorder = termSets.CustomSortOrder.Split(':');
           foreach (var item in taxorder)
           {
                   Term oTerm = termSets.GetTerm(new Guid(item));
                   //...
            }
}
else
{
            foreach (var item in termSets.Terms)
            {
                  //....
             }
}

E fino qui, tutto ok, ma non c'è nessuna proprietà che mi permetta di indicare a SharePoint di prendere i termini ai quali ho applicato il Custom Sort Order e avere in output la stessa struttura dati, ma dovrò vedere se la stringa CustomSortOrder è diversa da null... e indovinate, se non è null avete la lista dei termini ma solamente nel formato Guid:Guid:Guid......

Mah...

Comunque alla fine quello che conta è che è stato un kid's stuff

Pillole di SharePoint Management Shell

Altro comando che sto utilizzando molto è quello relativo all'attivazione di una feature:

stsadm -o activatefeature -name [folder name] -url [webapplication]

Kid's stuff

Pillole di SharePoint Management Shell

Altro reminder su un comando molto importante, la cancellazione di un site.

Remove SP-Web

Remove-SPWeb "http://macchina/subsite"

Kid's stuff

Pillole di SharePoint Management Shell

Visto che lo sto utilizzando molto in questo periodo e come al solito mi dimentico le basi :( me lo posto sul blog come reminder :D

Install Solution

stsadm -o addsolution -filename [percorso del file wsp]

Upgrade Solution

stsadm -o upgradesolution -name [nome del package] -filename [percorso del file wsp] -immediate -allowgacdeployment

Install Feature *

stsadm -o installfeature -filename "[feature name]\feature.xml"

* Nota per l'installazione della feature: il file Feature.xml si trova nel percorso "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\FEATURES"

Month List