Sunday, October 24, 2010

Estrarre il contenuto di un WSP via PowerShell

Lavorando per un progettino che penso nel giro di qualche settimana verrà pubblicato su CodePlex (per ora non posso dire di più) avevo la necessità di estrarre il contenuto di un WSP su file system. Come sappiamo i WSP sono dei CAB rinominati e l'estrazione del contenuto non è cosi semplice se effettuata tramite C# o VB.NET forse ancora meno com PowerShell. Queste almeno erano le sensazioni che avevo prima di approcciare il problema.
Ho fatto qualche ricerca ottenendo questi risultati:
  • molti suggerivano di farlo a mano da file system
    [grazie ottimo suggerimento! :X]
  • lavorare direttamente in C++ o di sfruttare l'SDK per interagire con questo formato. [soluzione un pò lunga, preferirei trovare qualcosa di più veloce]
  • alcuni hanno realizzato delle librerie managed da usare come facilitatori vedi http://www.codeproject.com/KB/files/CABCompressExtract.aspx
    [interessante esempio, ma serve portare dentro tutta sta libreria per solamente estrarre dei file da un CAB? Magari no]
  • creare un progetto VS e aggiungere una reference a Shell Automation COM component. Come suggerito sul questo thread http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/58d9cf34-9a43-4d58-96c7-44bff69ca5d4
    [devo comunque compilare con VS. Tra l'altro lo snippet segnalato non espande il CAB tenendo in considerazione la struttura folder/subfolder originaria)
  • usare un powershell che si occupa di estrarre il contenuto del CAB su un path generando la struttura a subfolder in esso contenuta. Ho trovato lo snippet powershell sul KB 2292741 "How to manually update scan engines in Microsoft Forefront Protection for Exchange Server or Microsoft Forefront Protection for SharePoint" (non fatevi inflenzare dal titolo del KB :-)
    [finalmente! era quello che stavo cercando! tempo risparmiato: un'enormità!]

Per semplicità riporto la funzione PowerShell che si occupa di estrarre il contenuto di un CAB (WSP rinominato) su file system.

# Use the Shell.Application COM object to extract the
# contents of the sourceCabPath and put the contents into
# the destinationDirectory. Support is included for cab
# files with sub directory hierarchies.
function ExtractCab($sourceCabPath, $destinationDirectory)
{
    # Determine if we can call the expand.exe utility
    # if so use it, otherwise, use the Shell.Application
    # COM object to perform the expansion of the CAB   
    & "expand.exe"
    
    if($?)
    {
        
        & "expand.exe" "-R" $sourceCabPath "-F:*" $destinationDirectory
    }
    else
    {
    
        $shell = new-object -comobject $ShellProgId

        if(!$?)
        {
            $(throw "unable to create $ShellProgId object")
        }

        $source = $shell.Namespace($sourceCabPath).items()

        $destination = $shell.Namespace($destinationDirectory)

        $flags = $DoNotDisplayProgress + $YesAll + $NoConfirmDirectory + $NoUI
        $itemCount = $source.Count
        $cabNameLength = $sourceCabPath.Length
        
        $cachedDestDir = ""    
        $relativeDest = ""
        
        # Process each item in the cab. Determine if the destination
        # is a sub directory and create if necessary.
        for($i=0; $i -lt $itemCount; $i++)
        {
            $lastPathIndex = $source.item($i).Path.LastIndexOf("\");
            
            # If the file inside the zip file should be extracted
            # to a subfolder, then we need to reset the destination
            if ($lastPathIndex -gt $cabNameLength)
            {
                $relativePath = $source.item($i).Path.SubString(($cabNameLength + 1), ($lastPathIndex - $cabNameLength))
                $relativeDestDir = $destinationDirectory + $relativePath
                
                if ($relativeDestDir -ne $cachedDestDir)
                {
                    $relativeDest = $shell.Namespace($relativeDestDir)
                    $cachedDestDir = $relativeDestDir
                }
                
                $relativeDest.CopyHere($source.item($i), $flags)
            }
            else
            {
                $destination.CopyHere($source.item($i), $flags)
            }
        }
    }
}

Thursday, October 21, 2010

Aggiornare programmaticamente un campo lookup multi value


Capita spesso di imbattersi in un problema che riguarda la scrittura di valori in un field di tipo lookup multi value. In particolare se cerca di  valorizzare la collezione del multi value un pò alla volta senza ogni volta salvare l'item, più o meno come riportato qui sotto, si incapperà nella exception Unable to cast object of type 'System.String' to type 'Microsoft.SharePoint.SPFieldLookupValueCollection'.


SPListItem item = getItem();
string fieldName = "LookupMultiValueField";

//Code block 1
SPFieldLookupValueCollection fieldLookupValues = (SPFieldLookupValueCollection)item[fieldName ]; // ok
fieldLookupValues.Add(new SPFieldLookupValue(1, "value1"));
item[fieldName ] = fieldLookupValues;

//Code block 2
fieldLookupValues = SPFieldLookupValueCollection)item[fieldName]; // Exception
item.Update();



Nel primo blocco di codice viene letto un field, settato il primo valore della collection e successivamente salvata la collection sul item. Il secondo blocco genera la exception che segnalavo prima. Questo perchè una volta modificato o inserito un valore su field lookup multi value la sua tipizzazione torna ad essere string (che di fatto è come viene gestito internamente). In pratico è l'oggetto SPListItem che si occupa di mappare la stringa 1;#value1;2;#value2 di un oggetto field lookup multi value tipizzato a SPFieldLookupValueCollection sia in fase di inizializzazione che successivamente ad un update.

Per risolvere il problema occorre quindi eseguire il metodo di Update del SPListItem dopo aver inserito/aggiunto un valore alla collection. Il codice diventa:


SPListItem item = getItem();
string fieldName = "LookupMultiValueField";

//Code block 1
SPFieldLookupValueCollection fieldLookupValues = (SPFieldLookupValueCollection)item[fieldName]; // ok
fieldLookupValues.Add(new SPFieldLookupValue(1, "value1"));
item[fieldName] = fieldLookupValues;
item.Update();

//Code block 2
fieldLookupValues = (SPFieldLookupValueCollection)item[fieldName]; // ok
fieldLookupValues.Add(new SPFieldLookupValue(2, "value2"));
item[fieldName] = fieldLookupValues;
item.Update();


Come Best Practice consiglio di lavorare sempre con un oggetto tipizzato SPFieldLookupValueCollection evitando nel modo più esclusivo di comporsi stringhe che seguono la sintassi dei campi di lookup.



Tuesday, October 12, 2010

Social Patterns and Interfaces

Segnalo un'interessante libro dal titolo Designing Social Interfaces scritto da Christian Crumlish (curatore della Yahoo! pattern library e evangelist Yahoo! Developer Network) e Erin Malone (esperta di user design, ex dipendete Yahoo! che ha lavorato nel team Managing experience design).Il libro non descrive solamente tematiche di user experince ma definisce anche una serie di linee guida, loro li chiamano pattern nel libro (anche se personalmente trovo talvolta un pò forzato questo termine), per la realizzazione di funzionalità core dei social network. Vengono trattate le diverse aree social a partire dalla parte dedicata alla persone (Engagement, Identity, Presence, Reputation) per poi passare ai servizi e la parte core delle iterazioni, ecc...
Molto interessante anche il sito del libro nel quale si possono trovare per ogni pattern degli esempi reali di utilizzo e link a siti/blog che trattano l'argomento.

Abstract

From the creators of Yahoo!’s Design Pattern Library, Designing Social Interfaces provides you with more than 100 patterns, principles, and best practices, along with salient advice for many of the common challenges you’ll face when starting a social website. Designing sites that foster user interaction and community-building is a valuable skill for web developers and designers today, but it’s not that easy to understand the nuances of the social web. Now you have help.

Christian Crumlish and Erin Malone share hard-won insights into what works, what doesn’t, and why. You’ll learn how to balance opposing factions and grow healthy online communities by co-creating them with your users.
- Understand the overarching principles you need to consider for every website you create
- Learn basic design patterns for adding social components to an existing site
- Rein in misbehaving users on an active community site
- Build a social experience around a product or service and invite people to join
- Develop a social utility without having to build an entirely new infrastructure
- Enable users of your site’s content to interact with one another
- Offer your members the opportunity to connect in the real world
- Learn to recognize and avoid antipatterns: emergent bad practices in the social network and social media space




Riferimenti
Sito del libro 'Designing Social Interfaces' http://www.designingsocialinterfaces.com
Yahoo! Social Patterns http://developer.yahoo.com/ypatterns/social/
Social Patterns & Best Practices http://designingsocialinterfaces.com/patterns/Main_Page

Monday, October 4, 2010

DocumentTemplate sulle library nuove e su quelle migrate

Problema
Dopo la migrazione da SharePoint 2007 a 2010 e un pò run-the-system, gli utenti si accorgono che sulle document library migrate si riescono a creare direttamente dalla UI di SharePoint nuovi documenti con Office 2003 e versioni successive mentre sulle nuove document library si possono creare documenti solo con Office 2007 e versioni successive.

Soluzione
Adottando una strategià di migrazione da SharePoint 2007 al 2010 di tipo detach e attach del content database ci si trova in una situazione in cui sulle document library migrate è presente il template di documento originario, per intenderci quello che era stato impostato su SharePoint 2007 dove il template di default è Forms/Document/template.doc mentre sulle document library create sul nuovo il template di default è Forms/template.dotx
Nonostate esista un Office Converter Pack per Office 2003 che rende possibile l'apertura dei documenti docx purtroppo il formato dotx non è gestito rendendo quindi impossibile l'apertura di template di documenti dotx su Office 2003.

Di fatto il template di lista document library OOB di SharePoint 2010 non permette di creare un nuovo documento dalla UI tramite il bottone New document sulla ribbon se il client ha installato Office 2003. Per dare rendere possibile la creazione di documenti con Office 2003 direttamente dai link su SharePoint 2010 occorre modificare il template di default. Come?
  • Creare una nuova list definition chiamandola ad esempio 'Office 2003 document library' che eredita lo schema di una lista di tipo document library OOB e sostituire il template dotx con uno in formato dot. In questo modo si darà la possibilità agli utenti di scegliere se usare una lista con template di default per Office 2003 e versioni successive (custom) o Office 2007 e versioni successive (OOB)
  • Modificare la list definition di default di una lista document library OOB di SharePoint 2010 aggiungendo un nuovo template dot per dare la possibilità di scelta (magari facendo attenzione a quale scegliere come default) o anche semplicemente sistituendo quello esistente. Questa soluzione può essere implementata con feature che aggiunge un event receiver sui siti in cui si vuole avere questo comportamento. L'event receiver dovrà gestire l'evento di list creation e programmaticamente cambiare il template.