domenica 8 maggio 2011

I favolosi 40 Template

Come riportato da Igor nel suo blog, TechSol ha reso disponibili per SharePoint 2010 alcuni dei 40 Template che erano a disposizione per SharePoint 2007; per l'esattezza 22 template.
Buon divertimento

sabato 30 aprile 2011

Download delle guide di SharePoint 2010in formato CHM

Sono stati da poco messi a disposizione i file CHM dell'SDK di SharePoint 2010; Foundation e Server. Li potete trovare ai seguenti indirizzi.
SharePoint Foundation 2010 CHM
SharePoint Server 2010 CHM

giovedì 28 aprile 2011

Creare Folder con un custom Content Type tramite event receiver

Ho trovato in internet che per creare via codice dei folder in una document library, con un content type custom che eredita da folder si deve usare il codice seguente:

SPWeb currentSite = (SPWeb)properties.Feature.Parent;
SPFolderCollection documentLibrary = currentSite.Lists["My Document Library"].RootFolder.SubFolders;
SPFolder folder = documentLibrary.Add("My Folder Name");
// set the content type id and update it, so that the proper attributes are present
folder.Item["ContentTypeId"] = "0x012000521AACBC415A498390B668D81308E454";
folder.Update();

Ma devo segnalare che questo codice non funziona, per generare un folder con assegnato correttamente il content type bisogna utilizzare un SystemUpdate al posto di un Update.

folder.Item.SystemUpdate();

mercoledì 27 aprile 2011

Recuperare informazioni sull'utente corrente in un form InfoPath 2010

Dopo una lunga pausa, mi ripropongo segnalando un ottimo articolo che spiega come fare ad usare in un form InfoPath 2010 pubblicato sotto SharePoint 2010 le informazioni sull'utente corrente, senza scrivere codice. Il tutto si risolve invocando il web service di SharePoint _vti_bin/UserProfileService.asmx. Quindi creando una Data Connection che si connette a questo Web Service è possibile recuperare le informazioni sull'utente, quali:
UserProfile_GUID
AccountName
FirstName
LastName
PreferredName
WorkPhone
Office
Department
Title
Manager
AboutMe
PersonalSpace
PictureURL
UserName
QuickLinks
WebSite
PublicSiteRedirect
SPS-Dotted-line
SPS-Peers
SPS-Responsibility
SPS-Skills
SPS-PastProjects
SPS-Interests
SPS-School
SPS-SipAddress
SPS-Birthday
SPS-MySiteUpgrade
SPS-DontSuggestList
SPS-ProxyAddresses
SPS-HireDate
SPS-LastColleagueAdded
SPS-OWAUrl
SPS-ResourceAccountName
SPS-MasterAccountName
Assistant
WorkEmail
CellPhone
Fax
HomePhone
Comunque è tutto ben spiegato nell'articolo; anche se si rifà a MOSS 2007 è del tutto valido anche per SharePoint 2010

mercoledì 2 febbraio 2011

PRO: Microsoft Office SharePoint 2010, Administrator

Missione compiuto, dopo la pausa Natalizia sono riuscito a dare l'esame di certificazione 70-668 "PRO: Microsoft Office SharePoint 2010, Administrator".
Il prossimo passo potrebbe essere affrontare gli esami per DEV. Tempo permettendo.

mercoledì 26 gennaio 2011

mercoledì 22 dicembre 2010

SharePoint 2010 - Workflow per cambiare i permessi ad un documento

Per un cliente avevo l'esigenza di rendere un documento immodificabile e non cancellabile.
In questo post ho intenzione di raccontarvi come ho risolto il problema con le nuove activity di SharePoint Designer 2010, con una sorpesa finale.
Vi raconterò:
  • Come creare i worflow per togliere e rimettere i permessi al documento
  • Come fare delle custom action accessibili dal ribbon con SharePoint Designer, per eseguire il workflow
  • Come crare delle custom action che tramite ECMA Client Object Model eseguono un workflow associato alla lista
Creare i workflow per manipolari i permissi
Prima di tutto si comincia creando un  nuovo Workflow con SharePoint Designer, molto comodi sono i Globally Reusable Workflow, che possono essere associati a verie liste e usati in tutti i siti della site collection (ricordatevi di premere il pulsante Publish Globally nel ribbon del Designer). Il primo step è quello di creare un Impersonation Step che ci consente di impersonare in fase di esecuzione un altro utente e nella fattispecie l'utente che ha creato il workflow. In questo modo è possibile far eseguire a utenti che non ne avrebbero i permessi determinate azioni.


L'azione da aggiungere è "Replece List Item permissions". Di seguito i passi per aggiungere, modificare o rimuovere permessi ad un item.









Per vedere tutte le immagini.
SP2010 Permission Workflow

Usando l'action "Inherit parent permissions for item..." è possibile riportare la situazione dei permessi alla condizione originale.

Quick Step con SharePoint Designer
Nel ribbon di ogni Library (tab Library), nella sezione "Customize Library" c'è il bottone "New Quick Step". Premendo questo bottone si apre SharePoint Designer sull'edit della lista corrente e viene visualizzato un pannello per configurare una custom action che sarà visibile nel tab Documents sotto la voce "Quick Steps". Da questa interfaccia è possibile associare all'azione: un link ad una pagina aspx del sito, l'avvio di un workflow associato alla lista oppure un link ad una URL esterna. Vedi fugure di seguito.




Custom Action per attivare un workflow
Visto che da Designer è possibile creare una custom action che consente di avviare un workflow, ho voluto provare a creare delle mie custom action,da posizionare in punti diversi del ribbon e con logiche di attivazione tutte mie, che facessero la stessa cosa. Il primo passo è stato cercare di capire quale azione fosse associata e questi Quick Step. Analizzando un po' il codice HTML generato e relativi Javascript ho scoperto, che queste actuion invocano la pagina "/_layouts/wfstart.aspx" con i seguenti parametri:
  • List={ListId}
  • ID={ItemId}
  • TemplateID
  • AssociationName
I primi due parametri sono chiari, l'ultimo è il nome che viene dato al workflow quando lo si associa alla lista, ma TemplateID ho fatto un po' più fatica a capire cosa fosse. Alla fine è risultato essere il GUID dell'associazione del workflow alla lista. Nei miei script è stato semplice ricavare i primi due parametri, l'ultimo ho deciso di metterlo staticamente nel codice (intanto stavo scrivendo action per attivare workflow che associavo io sempre con lo stesso nome), mentre il TemplateID ho sudato un po' a tirarlo fuori usando ECAM Client Object Model (ho spulciato un po' l'SDK). Alla fine il risultato è stato quanto segue:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction
  Id="Install JS"
  ScriptSrc="/_layouts/RibbonButtonsDemo/demo.js"
  Location="ScriptLink"
  Sequence="100">
  </CustomAction>
  <CustomAction Id="SharePoint.Ribbon.LockDocuments"
                Title="Lock Documents"
                Location="CommandUI.Ribbon"
                RegistrationType="List"
                RegistrationId="101">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition Location="Ribbon.Documents.Manage.Controls._children">
          <Button Id="Ribbon.Documents.New.MsysLockDocsButton"
          Alt="Lock selected document"
          Sequence="5"
          LabelText="Lock"
          Image16by16="/_layouts/images/RibbonButtonsDemo/lock-icon16x16.png"
          Image32by32="/_layouts/images/RibbonButtonsDemo/page-lock-icon32x32.png"
          Command="Command.LockButton"/>
        </CommandUIDefinition>
        <CommandUIDefinition Location="Ribbon.Documents.Manage.Controls._children">
          <Button Id="Ribbon.Documents.New.MsysUnlockDocsButton"
          Alt="Unlock selected document"
          Sequence="5"
          LabelText="Unlock"
          Image16by16="/_layouts/images/RibbonButtonsDemo/lock-off-icon16x16.png"
          Image32by32="/_layouts/images/RibbonButtonsDemo/page-lock-off-icon32x32.png"
          Command="Command.UnlockButton"/>
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <CommandUIHandler Command="Command.LockButton"
                          CommandAction="javascript:ExecuteOrDelayUntilScriptLoaded(LockDocs, 'sp.js');"
                          EnabledScript="javascript:onlyOne();"/>
        <CommandUIHandler Command="Command.UnlockButton"
                          CommandAction="javascript:ExecuteOrDelayUntilScriptLoaded(UnLockDocs, 'sp.js');"
                          EnabledScript="javascript:onlyOne();"/>
      </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>
</Elements>
Tutto il codice Javascript lo messo in un file js esterno salvato nella cartella layouts.

var context;
function LockDocs() {
    // First get the context and web
    context = SP.ClientContext.get_current();
    
    this.web = context.get_web(); 
    // Get the current selected list, then load the list using the getById method of Web (SPWeb)
    var listId = SP.ListOperation.Selection.getSelectedList();
    var sdlist = this.web.get_lists().getById(listId);
    this.wfa = sdlist.get_workflowAssociations().getByName('Change permissions');
    context.load(this.wfa);
    context.executeQueryAsync(Function.createDelegate(this, this.OnSuccess), Function.createDelegate(this, this.OnFail));
}

function UnLockDocs() {
    // First get the context and web
    context = SP.ClientContext.get_current();

    this.web = context.get_web();
    // Get the current selected list, then load the list using the getById method of Web (SPWeb)
    var listId = SP.ListOperation.Selection.getSelectedList();
    var sdlist = this.web.get_lists().getById(listId);
    this.wfa = sdlist.get_workflowAssociations().getByName('Re-active Permissions');
    context.load(this.wfa);
    context.executeQueryAsync(Function.createDelegate(this, this.OnSuccess), Function.createDelegate(this, this.OnFail));
}

function OnSuccess(sender, args) {
    var listId = SP.ListOperation.Selection.getSelectedList();
    // Get the currently selected item of the list. This will return a dicustonary with an id field
    var items = SP.ListOperation.Selection.getSelectedItems(context);
    var mijnid = items[0];
    var itemId = mijnid.id;
    var _url = '/_layouts/wfstart.aspx?List=' + listId + '&ID=' + itemId + '&TemplateID=' + this.wfa.get_id() + '&AssociationName=Change permissions';
    alert(_url);
    document.location = _url;
}

function OnFail(sender, args) {
    alert('Fail');
    //alert('Fail to get list list. Error :'+ args.get_message());
}

function moreThanOneEnabled() {
    var items = SP.ListOperation.Selection.getSelectedItems();
    var ci = CountDictionary(items);
    return (ci > 0);
}

function onlyOne() {
    var items = SP.ListOperation.Selection.getSelectedItems();
    var ci = CountDictionary(items);
    return (ci == 1);
}

Come potete vedere recupero il TemplateID dal workflow associato, nel metodo di feedback OnSuccess.

Sorpresa
La sorpresa consiste nel fatto che in SharePoint 2010 esiste una nuova funzionalità (una feature da attivare a livello di site collection), chiamata "In place Record", che fa proprio quello che serviva al mio cliente e in modo più elegante. Quindi tutta la fatica fatta è servita solo a titolo di studio :-)

martedì 9 novembre 2010

TS: Microsoft Office SharePoint 2010, Configuring

Passato con successo anche l'esame 70-667 "Microsoft Office SharePoint 2010, Configuring".
Adesso proverò ad affrontare il 70-668.

domenica 3 ottobre 2010

Inviare una mail a SharePoint

Credo che la maggior parte degli utilizzatori sappia che è possibile inviare una mail ad una lista di SharePoint, dopo aver configurato l'incoming mail nella console di ammministrazione. Ma forse non tutti sanno che solo alcune liste sono preproste a ricevere mail. Per esempio la custom list non può ricevere mail OOTB, ovvero non ha un handler associato OOTB.
Le liste che possono ricevere mail sono:
  • Announcements
  • Events (Calendar)
  • DocumentLibrary
  • PictureLibrary
  • XMLForm
  • DiscussionBoard
  • Blog
Questo in SharePoint 2007 di certo e sono sicuro che poco sia cambiato in SharePoint 2010 sotto questo punto di vista. Non ho provato tutte le liste, ma di certo la più interessante è l'Announcements che consente di vedere la mail completa nel campo Body, il Subject nel campo Title e gli allegati della mail, come allegati dell'item.
Per completare il discorso vi elenco gli Handler preprosti per ogni lista:
  • Announcements - SPAmmouncementsEmailHandler
  • Events - SPCalendarEmailHandler
  • DocumentLibrary, PictureLibrary e XMLForm - SPDocLibEmailHandler
  • DiscussionBoard - SPDiscussionEmailHandler
  • Blog - SPBlogPostEmailHandler
Alle liste che non derivano da un BaseTemplate  viene associato un SPExternalEmailHandler.
E' possibile infine associare ad una custom list un event receiver per gestire la ricezione di una mail implementando il metodo EmailReceived della classe SPEmailEventReceiver.

giovedì 30 settembre 2010

Come creare una form Infopath che decida a runtime la Save Location

Titolo un po' criptico ma richiama una problematica che ho dovuto affrontare in questi giorni. Dovevo creare una form InfoPath da usare via browser in SharePoint 2010, che salvasse il suo contento in una raccolta documentale in formato XML; niente di difficile direte voi, basta creare una Data Connection verso la lista SharePoint in cui si vuole salvare il risultato. Esatto, ma la mia document library stava in un sito che andava salvato in un template. Come nella maggior parte delle volte, niente programmazione, niente site definition; tutto creato da browser e con InfoPath 2010 Designer. Dopo una giornata di tentativi (premetto che le mie conoscenze di InfoPath tendevano a meno infinito) leggendo nell'event viewer del server ho scoperto come viene invocata la form InfoPath e da questo, aggiungendo alcuni consigli di Alberto Ballabio (trovati sul suo sito SGART)  e di Massimo Luinetti e un pizzico di aiuto reperito da questo articolo ho scritto del codice per risolvere il mio problema.
Innanzi tutto cosa ho trovato nell'event viewer. Quando creo la form la url invocata è del tipo:
http://<ServerName>/_layouts/FormServer.aspx?XsnLocation=http://srvsp01/FormServerTemplates/formName.xsn&SaveLocation=http://<ServerName>/nav/temp/Changes&Source=http://<ServerName>/nav/temp/Changes/Forms/AllItems.aspx&DefaultItemOpen=1
Quello che ci interessa è il parametro SaveLocation.
Quando riapro il form invece la url invocata è:
http://<ServerName>/_layouts/FormServer.aspx?XmlLocation=/nav/temp/Changes/formName.xml&Source=http://<ServerName>/nav/temp/Changes/Forms/AllItems.aspx&DefaultItemOpen=1
Quindi le due url non sono uguali. Detto questo la soluzione prevede di cambiare a runtime la url della Data Connection di tipo Submit. Anche in questo caso c'è l'inghippo. Infatti ho scoperto a mie spese, che l'unico evento in grado di modificare con successo la url della Data Connection è l'evento di Loading.
Ecco il codice finito e funzionante.
using Microsoft.Office.InfoPath;
using System;
using System.Xml;
using System.Xml.XPath;

namespace caricafiles
{
    public partial class FormCode
    {
        private object _strUri
        {
            get
            {
                return FormState["_strUri"];
            }
            set
            {
                FormState["_strUri"] = value;
            }
        }


        // NOTE: The following procedure is required by Microsoft Office InfoPath.
        // It can be modified using Microsoft Office InfoPath.
        public void InternalStartup()
        {
            EventManager.FormEvents.Loading += new LoadingEventHandler(FormEvents_Loading);
        }

        public void FormEvents_Loading(object sender, LoadingEventArgs e)
        {
            Init(e);       
        }

        // Modificare la url della data connection è possibile solo nel metodo FormEvents_Loading
        private void Init(LoadingEventArgs e)
        {
            //Get the Uri (or SaveLocation in a browser form) of where 
            //the form was opened 
            //See if the form was opened in the browser
            Boolean OpenedInBrowser = Application.Environment.IsBrowser;
            //Get a reference to the submit data connection
            FileSubmitConnection fc = (FileSubmitConnection)this.DataConnections["FOB Save"];

            //If so, we will get the "SaveLocation" from the InputParameters
            if (OpenedInBrowser)
            {
                if (e.InputParameters.ContainsKey("SaveLocation"))
                {
                    _strUri = e.InputParameters["SaveLocation"].ToString();
                }
                else if (e.InputParameters.ContainsKey("Source"))
                {
                    string source = e.InputParameters["Source"].ToString();
                    _strUri = source.Substring(0, source.IndexOf("Forms") - 1);
                }
                else
                {
                    _strUri = _strUri;
                }
            }
            else
            {
                //If it was opened in the client, we will get the Uri
                _strUri = this.Template.Uri.ToString();
            }

            //Modify the URL we want to submit
            fc.FolderUrl = (string)_strUri;
        }
    }
}
Ultima nota: tenete in considerazione la lingua di InfoPath client quando andate a cercare aiuto un internet perchè i nodi della form InfoPath vengono localizzati e quindi il codice XPath di un nodo di una form in inglese risulta diverso da quello di una form in italiano.

martedì 21 settembre 2010

Vulnerabilità di SharePoint 2010

Il team di SharePoint sul proprio blog ha annunciato una vulnerabilità del framework .NET che impatta anche su SharePoint e consiglia di adottare subito il workaraound descritto nell'articolo stesso.
In sostanza bisogna seguire i seguenti passi:
  1. Posizionarsi nella cartella %CommonProgramFiles%\Microsoft Shared\Web Server Extensions\14\template\layouts. 
  2. Creare un nuovo file chiamato error2.aspx nella cartella contenente il seguente codice:


    <%@ Page Language="C#" AutoEventWireup="true" %>
    <%@ Import Namespace="System.Security.Cryptography" %>
    <%@ Import Namespace="System.Threading" %>
    
    <script runat="server">
       void Page_Load() {
          byte[] delay = new byte[1];
          RandomNumberGenerator prng = new RNGCryptoServiceProvider();
    
          prng.GetBytes(delay);
          Thread.Sleep((int)delay[0]);
            
          IDisposable disposable = prng as IDisposable;
          if (disposable != null) { disposable.Dispose(); }
        }
    </script>
    
    
    <html>
    <head runat="server">
        <title>Error</title>
    </head>
    <body>
        <div>
            An error occurred while processing your request.
        </div>
    </body>
    </html> 
    
    
  3. Posizionarsi nella cartella %SystemDrive%\inetpub\wwwroot\wss\virtualdirectories
  4. Per ogni sottocartella fare i seguenti passi:



    1. Edit web.config
    2. Trovare il nodo "customErrors" e cambiarlo come segue


      <customErrors mode="On" redirectMode="ResponseRewrite" defaultRedirect="/_layouts/error2.aspx" />
      
    3. Salvare le modifiche
    4. Eseguire iisreset /noforce

giovedì 2 settembre 2010

SharePoint 2010 - Esempio di ECMA Client Object Model

Ero curioso di provare il nuovo Client Object Model di SharePoint 2010, così ho scritto qualche riga di codice per provare a listare tutti i siti di una site collection. Avendo trovato un po' di difficoltà a fare questa semplice operazione, in quanto ho trovato poco chiare le informazioni presenti nello SDK, ho anche cercato una funzione Javascript che mi consentisse di ispezionare gli oggetti dell'ECMA COM (la funzione l'ho trovato su questo sito).

<script type="text/javascript">
ExecuteOrDelayUntilScriptLoaded(initialize, "sp.js");
var context = null;  
var web = null;
var collWeb = null;

/*function initialize()
{
 context = new SP.ClientContext.get_current();  
 web = context.get_web();
 context.load(web);
 context.executeQueryAsync(Function.createDelegate(this, this.onSuccessMethod), Function.createDelegate(this, this.onFailureMethod));
}

function onSuccessMethod(sender, args) {  
 var d = document.getElementById("list");
 var l = document.createElement("LI");
 l.innerHTML = "<span>" + web.get_title() + "</span>";
 d.appendChild(l);
}*/ 

function initialize()
{
 context = new SP.ClientContext.get_current();  
 this.collWeb = context.get_web().get_webs();
 context.load(this.collWeb);
 context.executeQueryAsync(Function.createDelegate(this, this.onSuccessMethod), Function.createDelegate(this, this.onFailureMethod));
}

function onSuccessMethod(sender, args) {  
 
 var d = document.getElementById("list");
 var web = null;
 for(var x=0; x < this.collWeb.get_count(); x++)
 {
  var l = document.createElement("LI");
  web = this.collWeb.itemAt(x);
  l.innerHTML = "<span>" + web.get_title() + "</span>";
  d.appendChild(l);
 }
 
 var d2 = document.getElementById("test");
 var d1 = document.createElement("DIV");
 //d1.innerHTML = inspect(this.collWeb.itemAt(0),1,0);
 d1.innerHTML = inspect(this.collWeb,1,0);
 d2.appendChild(d1);
} 

 
function onFaiureMethodl(sender, args) {  
     alert('request failed ' + args.get_message() + '\n' + args.get_stackTrace());  
} 

function inspect(obj, maxLevels, level)
{
  var str = '', type, msg;

    // Start Input Validations
    // Don't touch, we start iterating at level zero
    if(level == null)  level = 0;

    // At least you want to show the first level
    if(maxLevels == null) maxLevels = 1;
    if(maxLevels < 1)     
        return '<font color="red">Error: Levels number must be > 0</font>';

    // We start with a non null object
    if(obj == null)
    return '<font color="red">Error: Object <b>NULL</b></font>';
    // End Input Validations

    // Each Iteration must be indented
    str += '<ul>';

    // Start iterations for all objects in obj
    for(property in obj)
    {
      try
      {
          // Show "property" and "type property"
          type =  typeof(obj[property]);
          str += '<li>(' + type + ') ' + property + 
                 ( (obj[property]==null)?(': <b>null</b>'):( '' )) + '</li>';

          // We keep iterating if this property is an Object, non null
          // and we are inside the required number of levels
          if((type == 'object') && (obj[property] != null) && (level+1 < maxLevels))
          str += inspect(obj[property], maxLevels, level+1);
      }
      catch(err)
      {
        // Is there some properties in obj we can't access? Print it red.
        if(typeof(err) == 'string') msg = err;
        else if(err.message)        msg = err.message;
        else if(err.description)    msg = err.description;
        else                        msg = 'Unknown';

        str += '<li><font color="red">(Error) ' + property + ': ' + msg +'</font></li>';
      }
    }

      // Close indent
      str += '</ul>';

    return str;
}
</script>

<div id="test">
Elenco dei siti della site collection listati tramite ECMAScript Client Object 
Model<br /> 
<ul id="list"></ul>
</div>

lunedì 30 agosto 2010

Rilasciati i primi updates per SharePoint 2010

Riporto l'informazione riportata da Igor ormai cinque settimane, che sono state rilasciate le prime hotfix per SharePoint 2010. Per approfondimenti vi rimando all'articolo di Igor.

martedì 24 agosto 2010

Feature upgrade

Vi segnalo un interessante articolo (in vero una serie di 4) sulla nuova caratteristica delle Caratteristiche (scusate il gioco di parole) di Sharepoint 2010; ovvero la possibilità di upgradare le feature, cioè di aggiungere delle modifiche alle nostre feature che sono già state deployate cambiandone versione. L'articolo è di Chris O'Brien e si intitola Feature upgrade.

Shapoint Designer 2010 - Globally Reusable Workflows

Recentemente ho lavorato con Sharepoint Designer 2010 per creare dei workflow reusable, apprezzandone la malleabilità. Dopo un primo smarrimento nell'uso della nuova interfaccia, non ho trovato grossi problemi a creare i mie workflow, avvalendomi anche delle nuove "actions". Faccio notare che quando si crea un reusable workflow di default Sharepoint Designer lo distribuisce con scope a livello si sito web. Per poter creare un globally reusable workflow, ovvero un workflow riutilizzabile all'interno della site collection è necessari in fase di pubblicazione selezionare il bottone "Publish Globbaly" presente nel ribbon di Sharepoint Designer (tab Workflows), come mostrato in figura.


Elenco novità in Sharepoint Designer 2010 sui workflow.
Nuove caratteristiche di SharePoint 2010 Workflow.

martedì 3 agosto 2010

Nuova certificazione su SharePoint

Oggi ho passato l'esame di certificazione 70-631, Wss3.0 Configuring.

Mi sa che ormai mi tocca affrontare le certificazioni sulla nuova versione...

domenica 20 giugno 2010

SharePoint 2010 - Requested registry access is not allowed

Controllando l'event viewer del mio server di SharePoint 2010 con installazione Standalone ho visto molti errori di questo tipo "Requested registry access is not allowed".

Cercando in internet ho trovato che la soluzione è ridare i permessi a Network Service a queste 2 chiavi di registro.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\14.0\Secure\FarmAdmin
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates


Non ho ancora capito il motivo del mal funzionamento.

venerdì 11 giugno 2010

Finalmente la certificazione MCTS

Finalmente ho preso il coraggio a due mani e ho affrontato l'esame di certificazione 70-630.

E' con immenso piacere che posso annunciare di essere riuscito a passarlo con un punteggio di 964, quindi ora sono MCTS - Microsoft Office SharePoint Server 2007, Configuration.

giovedì 3 giugno 2010

SharePoint 2010 and Office Web Apps

Non so se ritenermi fortunato o meno, per aver già installato due volte le office web apps su SharePoint 2010, perchè per ora l'esperienza è stata più simile a un parto plurigemellare che a una passeggiata.

Premetto che ho seguito fedelmente quanto riportato nell'articolo Technet di Microsoft Deploy Office Web Apps (Installed on SharePoint 2010 Products), ma in entrambe i casi le cose non sono andate per nulla liscie.

La prioma istallazione l'ho fatta su una macchina con SharePoint 2010 installato in standalone mode; in questo caso dopo aver installato i binari delle web apps ho lanciato il PSConfig che arrivato allo step 7 di 10 si è bloccato (suppongo stesse cercando di registrare l'avvenuta istallazione delle web apps in active directory, peccato che la macchina non fosse in dominio). Non sapendo come procedere ho stoppato il processo e sono andato a controllare fin dove era arrivato il configuratore; strano ma vero ho trovato tutti i servizi configurati e startati e le web service application anch'esse pronte all'uso. In definitiva le office web apps risultano funzionanti, anche se il processo PSConfig non ha mai visto la fine.

Di tutt'altro spessore il secondo caso. La situazione era una macchina di front end con sharepoint che punta a un server con SQL Server, questa volta in dominio. Dopo aver installato i binari ho lanciato PSConfig che questa volta è arrivato fino all'ultimo step ma con un bel FAILED! Pensando di essere fortunato come la prima volta sono andato a vedere lo stato dei servizi e delle web service application, ma questa volta la fortuna mi ha voltato le spalle. I servizi erano configurati ma stoppati e le web service application non erano state create. A questo punto ho pensato di lanciare gli script suggeriti nell'articolo e mi sono ritrovato con le web service application installate e configurate in stato di started, ma con i servizi in un limbo: starting.

Dopo essermi consultato con un po' di persone, Davide Colombo mi ha suggerito di eliminare i servizi rimasti appesi con il seguente comando STSADM: deleteconfigurationobject (Unable to delete Shared Services).

Use the following procedure to identify the Shared Services GUID:
  • Login to SQL server.
  • Open SQL Management Studio and expend Databases.
  • Expand Configuration Database & Tables.
  • Opened table for dbo.object.
  • Executed following query in query analyzer: SELECT * FROM [MOSS_CFG_CA_01].[dbo].[Objects]where name like ‘Name of the Shared Services’.
  • Copy the ID of object referenced in objects table of configuration database.
Dopo aver eliminato i servizi in startig ho lanciato nuovamente la configurazione di SharePoint per tornare alla situazione iniziale con i servizi stoppati, a questo punto da browser li ho attivati, con esito positivo. Il risultato è stato che le Office Web Apps di Word e Excel funzionano, mantre quella di PowerPoint va in errore quando cerca di aprire un file PowerPoint.

Per ora questo ultimo mistero non è ancora stato risolto, invito chiunque avesse un'idea di offrire il suo contributo. Aggiornerò il post qualora trovassi una soluzione.

lunedì 3 maggio 2010

Publishing Site con WSS 3.0 – 3

Riprendendo il discorso dell’ultimo articolo, analizzeremo le funzionalità aggiunte al sito, utili per l’editore e la soluzione adottata per la gestione della Quick Launch.

New Web Part Page

Per creare una nuova pagina di tipo web part con lo stesso template della pagina di default del sito, ho dovuto personalizzare l’application page che gestisce la creazione delle pagine. Per la pagina custom di creazione delle web part page ho seguito quanto riportato in questo articolo Creating Custom Web Part Page Templates for Microsoft SharePoint Products and Technologies; l’unico dettaglio, come riportato da me nel forum su SharePoint (Web Part Page Template ?!?!), è che se si vogliono aggiungere i propri template di pagina ad una custom site definition bisogna creare nella cartella 1033 o 1040 una cartella con il nome della propria site definition e aggiungere la sottocartella DOCTEMP con all’interno la cartella SMARTPGS e in questa porre i propri template.



Master Page


In questo sito volevo che le visualizzazioni pubbliche avessero una master page diversa dalla master page delle pagine di servizio, dove con pagine di servizio intendo le pagine di gestione delle liste, non solo le pagine amministrative (ad es. /_layouts/Settings.aspx). Per ottenere questo risultato ho sfruttato il concetto di ~masterurl/default.master e ~masterurl/custom.master offerto da SharePoint. L’argomento l’ho già affrontato nel post Custom Master Page. Per questo motivo nella pagina che consente l’associazione delle master page ci sono due voci, default e custom. Il check box “Set children master page” permette di impostare la master page scelta anche a tutti i sotto siti del sito a cui si sta cambiando la master page.




Welcome Page


L’ultima funzionalità utile per l’amministratore del sito, presente in MOSS, ma non in WSS è la pagina che permette di associare al sito una nuova welcome page, diversa dalla pagina default.aspx. Purtroppo la funzionalità è veramente minimale nella sua realizzazione, pertanto è necessario digitare a mano la url della nuova welcome page. Questa infine sarà un nuova web part page, memorizzata nella lista Pages del sito. Il codice per cambiare la welcome page usando l’Object Model di SharePoint è il seguente:

using (SPSite siteCollection = new SPSite(http://server/sites/sandbox)) {
using (SPWeb site = siteCollection.RootWeb) {
SPFolder rootFolder = site.RootFolder;
rootFolder.WelcomePage = "Pages/home.aspx";
rootFolder.Update();
}
}

Per onestà intellettuale vi posto anche l’url del blog che mi ha spiegato come fare: Setting the Welcome Page in WSS 3.0.



Quick Launch

Per risolvere il problema della Quick Launch ho deciso di affidarmi a un Event Handler (PagesReceiver) associato alla lista Pages del mio sito. Il compito dell’Event Handler è quello di intercettare le azioni compiute sugli item della lista e di reagire di conseguenza. L’Event Handler della lista Pages crea una nuova entry nella Quick Launch in una posizione predefinita, ovvero come figlia dell’heading Pages (un articolo utile HOW TO: Programmatically customize site navigation in WSS 3.0 and MOSS 2007). Possiamo dire che questa è la parte semplice dell’eserc izio, anche se un problemino invero c’è. Quando creo una pagina con la pagina descritta precedentemente i metadati dell’item non sono ancora stati valorizzati, pertanto il nome della pagina corrisponde al nome file, estensione compresa.




Per modificare la label associata all’item della Quick Launch è necessario rieditare l’item e impostare la proprietà Title, che all’atto della creazione sarà non valorizzato.






Un’altra semplice attività è la rimozione della voce di menu quando cancello una pagina dalla lista. La cosa difficile invece è gestire la possibilità di nascondere la pagina dalla Quick Launch, senza rimuoverla dalla lista Pages. Ho provato a creare un algoritmo che si basa sul metadato Visible, che ho aggiunto alla lista Pages, ma francamente, per il momento, ho ottenuto un risultato assai scarso. Se qualcuno avesse suggerimenti in merito sarei più che felice di riceverli, per trovare una soluzione migliore. Cercherò di descrivervi in breve l’idea: alla mia lista Pages personalizzata, oltre ad una colonna Visible ho aggiunto una colonna ParentIDs, in cui tengo traccia di tutti i Node ID che precedono il nodo corrente, ovvero la pagina appena inserita in quicklaunch; questa colonna contiene una stringa con i valori separati da punto e virgola.
Quando cancello una pagina questa viene semplicemente rimossa dalla quicklaunch (evento ItemDeleted), mentre se cambio la proprietà Visible da true a false, allora cancello il nodo dalla quicklaunch ma non dalla lista Pages, quando riporto la proprietà Visible al valore originale leggo la proprietà ParentIDs per stabilire la posizione in cui inserire il nodo. Di seguito il metodo che ri-aggiunge il nodo alla quicklaunch, come dicevo non è perfetto, ma per la maggior parte dei casi funziona bene.
private void AddNode2Navigation(SPWeb web, SPNavigationNode rootLink, SPNavigationNode node, string parentIDs)
{
string[] ids = null;
SPNavigationNode tmp = null;
if (!String.IsNullOrEmpty(parentIDs))
{
ids = parentIDs.Split(';');
for (int i = ids.Length; i > 0; i--)
{
tmp = web.Navigation.GetNodeById(Int32.Parse(ids[i - 1]));
if (tmp != null)
{
rootLink.Children.Add(node, tmp);
return;
}
}
}
// if parent ids are null.
if (rootLink.Children.Count > 1)
rootLink.Children.AddAsLast(node);
else
rootLink.Children.AddAsFirst(node);
}

Se qualcuno avesse suggerimenti da darmi per migliorare il metodo o per realizzarne uno migliore non si faccia scrupoli a contattarmi.