torsdag 15 november 2012

EPiServer Powershell deploy script

Tjipp! som Bengt Alsterlind säger här i Värmland (iaf inte hört någon annan använda ordet).

Blivit mycket powershell för min del på sistone. Men måste säga att jag gillar språket mer och mer, ett klart fall framåt från gamla dos-batch. Här kommer ytterligare lite powershell:

Tänkte presentera ett litet förslag på hur ett EPiServer powershell deploy script kan se ut. Alltså ett script som kopierar visst innehåll från en Visual Studio projekt-katalog till en annan katalog i syfte att kunna zippa ihop innehållet och flänga upp på en testserver/prod.server. Har läst att det ska finnas färdiga såna här templates i EPiServer 7 (vet inte om det är MSBuild eller PS), men vet inte mer om detta just nu.

<Dicsclaimer>Observera att alla webbplatser har sina egna krav och behov och att detta script antagligen inte överensstämmer med just dina krav.</Disclaimer> :P


# DeployPackage.ps1
# 2012-11-14 Määts

# Example call:
# ./DeployPackage.ps1 -verbose -c "Release" -s "C:\Path\To\My\EPiServer\Project\" -d "C:\Path\To\My\Deploy\Folder\"

# Script params
param([switch]$verbose, [string]$configurationMode, [string]$sourceDir, [string]$destDir)

$verboseoutput = @"
Executing script with params:
configurationMode: $configurationMode
sourceDir:         $sourceDir
destDir:           $destDir
"@

# Handle switches
if ($verbose.IsPresent) {
    Write-Host $verboseoutput
    $VerbosePreference = "Continue"
} else {
    $VerbosePreference = "SilentlyContinue"
}
# Validate params
if (-NOT($sourceDir -eq $NULL) -and (Test-Path $sourceDir) -eq $FALSE) {
    Write-Host "ERROR: Parameter ""sourceDir"" does not appear to be a valid path... aborting."
    Write-Host $sourceDir
    break
}

if (-NOT($destDir -eq $NULL) -and (Test-Path $destDir) -eq $FALSE) {
    Write-Host "ERROR: Parameter ""destDir"" does not appear to be a valid path... aborting."
    Write-Host $destDir
    break
}
if ($configurationMode -eq $NULL)
{
    Write-Host "ERROR: Parameter ""configurationMode"" not set... aborting."
    break
}
if (([string]$configurationMode).ToLower() -ne "release") {
    Write-Host "INFO: Parameter ""configurationMode"" not set to release mode... aborting."
    break
}

# Declare function
function DeployPackage() {
<#
    .SYNOPSIS
    Creates an EPiServer site deployment package
   
    .DESCRIPTION
   
    .PARAMETER
   
    .EXAMPLE
   
    .INPUTS
   
    .OUTPUTS
#>

    # Function params
    param([string]$configurationMode, [string]$sourceDir, [string]$destDir)
       
    # Variables
    $dateString = (Get-Date -format "yyyy-MM-dd_HH.mm.ss") # String representation of current date that will be used when naming the deploy package
    $fileTypes = @("*.aspx","*.ascx","*.jpg","*.gif","*.png","*.asmx","*.resx","*.xml","*.jpg","*.js","*.css","*.dll",
                   "*.pdb","*.swf","*.txt","*.html","*.htm","*README*","*.php","*.pdn","*.master","*.xsd","*.fla",
                   "*.as","*.asax") # This is the filetypes that the script will copy into the deploy
    $excludeFolders = @("obj")
    $excludeFiles = @("EPiServerErrorLog.txt")
                 
    $newDestDir = "$destDir\DeployPackage_$dateString\"
   
    # Create new folder
    New-Item $newDestDir -type directory
 
    #Get all files to copy
    $filesToCopy = Get-ChildItem $sourceDir -recurse -include $fileTypes
   
    # Copy items
    foreach ($file in $filesToCopy)
    {
        $newFilePath = $file.Fullname.Replace($sourceDir,$newDestDir)
       
        # Create new file (unix touch) in order to maintain folder structure
        New-Item -ItemType File -Path $newFilePath -Force
       
        # Copy file (overwrite)
        Copy-Item $file.Fullname $newFilePath -Force
    }
   
    # Remove unwanted folders
    Get-ChildItem $newDestDir -include $excludeFolders -recurse | Where-Object { $_.PSIsContainer } | Foreach-Object { Remove-Item $_.Fullname -recurse -Force }
   
    # Remove unwanted files
    Get-ChildItem $newDestDir -recurse -include $excludeFiles -Force | Foreach-Object { Remove-Item $_.Fullname }
}

# Call function
DeployPackage -c $configurationMode -s $sourceDir -d $destDir


Kommentar:
För att köra scriptet slänger man in följande i post-build events i Visual Studio för EPiServer webb-projektet.

 Powershell.exe -file "$(SolutionDir)DeployPackage.ps1" -ExecutionPolicy Unrestricted -verbose -c "$(ConfigurationName)" -d "C:\Deploy" -s "$(ProjectDir)\"

 Mitt script ligger alltså i "Solutiondir"-roten. Jag skickar in $(ConfigurationName) eftersom jag enbart vill att en deploy mapp ska skapas när projektet byggs i release-mode. Slutligen skickar jag med aktuell projektkatalog och vart jag vill att deploy-mappen ska skapas.

Som synes har jag slarvat lite i scriptet och inte orkat skriva klart kommentarer. Erkänner också villigt att #Remove unwanted files/folders inte är speciellt snyggt utan hade kunnat filtrerats redan i kopieringsstadiet. Det hade också varit snyggt om jag kunde avslutat med att zippa ihop mappen men detta är inte sååå jobbigt att göra själv jämfört med att sitta och hantera alla filtyper osv. 

Om nån läsare har kritik/bättre idéer så är jag idel öra, är som sagt fortfarande grön i powershell träsket :)

fredag 9 november 2012

SharePoint 2010 - Uppdatera Shared DataSource på alla rapporter i ett documentbibliotek mha Powershell

Aye Caramba!

Idag när jag köttade lite Powershell bestämde jag mig för att lösa ett jäkligt drygt problem jag haft i SharePoint när jag exporterat/importerat (backup/restore) en SiteCollection innehållandes massa rapporter och alla rapporter tappat sin DataSource-link. Vilket innebar massa manuellt klickande och klipp & klistrande.

Med mina nya powershell kunskaper kom jag fram till följande script:

1. Uppdaterar connectionString i min shared datasource och sätter lite andra variabler
2. Anropar SharePoints ReportServer-webbservice och ändrar datasource till ovan nämnda shared datasource på samtliga rapporter.

Nåväl, here goes (tänk på att du får ändra variablerna så de funkar i din miljö):

#Powershell
Clear-Host

# Add SharePoint snapin if needed
if ((Get-PSSnapin -Name Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue)     -eq $null)
{
    Add-PSSnapin Microsoft.SharePoint.Powershell
}

# Variables
$webApplicationUrl = "http://my.hostheader.com/"
$hostHeader = $webApplicationUrl -replace "http://", ""
$connectionString = "Data Source=MyDbServer;Initial Catalog=MyDataBase;Integrated    Security=SSPI;" 
$webApplicationUNCPath = "\\$hostHeader\DavWWWRoot\"
$dataSourcesLibraryName = "Data Sources"
$dataSourceDefinitionName = "MySharedDataSource.rsds"
$dataSourceLink =    "$webApplicationUrl/$dataSourcesLibraryName/$dataSourceDefinitionName"
$dataSourceUNCFilePath = $webApplicationUNCPath + $dataSourcesLibraryName + "\" +     $dataSourceDefinitionName
$reportsLibraryName = "MyReports"
$reportServerUri = "$webApplicationUrl/_vti_bin/ReportServer/ReportService2010.asmx?WSDL"

# Script
$site = Get-SPSite($webApplicationUrl)
$web = $site.OpenWeb()

$dataSources = $web.Lists | Where-Object { $_.Title -eq $dataSourcesLibraryName }
$dataSource = $dataSources.Items | Where-Object { $_.Name -eq   $dataSourceDefinitionName }
$reports = $web.Lists | Where-Object { $_.Title -eq $reportsLibraryName }

## Change properties on the shared data source object (through xml manipulation)
$xml = [xml](Get-Content $dataSourceUNCFilePath)
$xml.DataSourceDefinition.ConnectString = $connectionString
$xml.DataSourceDefinition.CredentialRetrieval = "Integrated"
$xml.DataSourceDefinition.Enabled = "True"
$windowsCredentialsNode = $xml.DataSourceDefinition.ChildNodes | Where-Object { $_.Name -eq "WindowsCredentials" }
if ($windowsCredentialsNode -ne $NULL)
{
    $xml.DataSourceDefinition.RemoveChild($windowsCredentialsNode)
}
$ImpersonateUserNode = $xml.DataSourceDefinition.ChildNodes | Where-Object { $_.Name -eq "ImpersonateUser" }
if ($ImpersonateUserNode -ne $NULL)
{
    $xml.DataSourceDefinition.RemoveChild($ImpersonateUserNode)}
$xml.Save($dataSourceUNCFilePath)

# Iterate through reports and set correct shared datasource
$Proxy = New-WebServiceProxy -Uri $reportServerUri -UseDefaultCredential ;

# Gather the webservice types for later use 
$WebServiceTypes = @{}
foreach ($Type in $Proxy.GetType().Assembly.GetExportedTypes())
{
    $WebServiceTypes.Add($Type.Name, $Type.FullName);
}

# Get list of all reports
$ReportItems = $Proxy.ListChildren("/", $true) | Where-Object { $_.Name -Like "*.rdl" } | Where-Object { $_.Path -Like "$webApplicationUrl/$reportsLibraryName" }
$dataSources = $web.Lists | Where-Object { $_.Title -eq $dataSourcesLibraryName }
$dataSource = $dataSources.Items | Where-Object { $_.Name -eq     $dataSourceDefinitionName }

# Declare DataSourceReference object
$ref = New-Object $WebServiceTypes.DataSourceReference
$ref.Reference = $dataSourceLink;

# Declare DataSourceReference object
$ds = New-Object $WebServiceTypes.DataSource
$ds.Name = $dataSource.DisplayName
$ds.Item = $ref

# Apply DataSource to all Reports
foreach ($ReportItem in $ReportItems)
{
    $dss = $Proxy.GetItemDataSources($ReportItem.Path)      
    $dss[0] = $ds
    Try 
    {
        $Proxy.SetItemDataSources($ReportItem.Path, $dss)
    }
    Catch [System.Exception]
    {
        "Error: Could not fix datasource for report: {0}" -f $ReportItem.Name
    }
}
$Proxy.Dispose()
$site.Dispose()

Edit #1: Kan vara intressant att veta att min sitecollection ligger på webbapplikationens rot-nivå också.
Edit #2: Bytte implementationen med att hämta objekt ur xml eftersom den gamla inte returnerade System.Xml.XmlElement objekt som fungerar ihop med RemoveChild-metoden. Rättade även så att enbart rapporter i ett utvalt dokumentbibliotek ($reportsLibraryName) "fixas" utav scriptet ifall man kör på en farm innehållandes massa olika rapporter.

Sådärja, jämt dretgött detta! :)

tisdag 6 november 2012

Fel vid installation av EPiServer Mail (Invalid Application Pool Name)

Ett annat problem som dök upp nyligen:

Skulle installera EPiServer Mail på en utvecklingsmiljö med EPiServer CMS 6 när följande fel inträffade (i EPiServer Deployment Center):




















Felet orsakas av att egenskapen "AppPoolName" aldrig vidarebefordras från EPiServer Mail-installationsscriptet. Att ändra i detta script så att det skickar med rätt variabel kräver dock manipulering utav objekt-medlemmar i powershell, något som jag inte bemästrar i skrivande stund.

Så ett enklare sätt att lösa felet är följande:

1. Ändra Powershell ExecutionPolicy

Eftersom alla EPiServer-ps-script är signade med nån hashhistoria behöver vi ändra executionpolicy på webbservern så att scriptet tillåts att köras även om vi modifierar det:

Börja med att köra:




Notera vad du får för svar (i detta fall "AllSigned").

Starta en PowerShell prompt och kör:





Svara ja på varningsmeddelandet som dyker upp.

2. Modifiera script "Install Site (SqlServer).ps1

Öppna upp IIS:en och kolla vad namnet på applikationspoolen är för den site där du ska installera EPiServer Mail.

Öppna filen "Install Site (SqlSErver).ps1" (samma fil som refereras till i felmeddelandet ovan).
Ändra rad 157 enligt (jag behöll den gamla implementationen för att kunna ändra tillbaks senare):




Vi ersätter alltså variabel-värdet med ett hårdkodat strängvärde som alltså ska motsvara namnet på applikationspoolen som vi nyss tog reda på.

3. Kör EPiServer Mail installation på nytt

Kör nu EPiServer Mail installationen på nytt via EPiServer Deployment Center. Denna gång bör det fungera!


4. Återställ

Ändra först tillbaks Install Site (SqlSErver).ps1 (ta bort den modifierade raden och återställ den utkommenterade raden).

Ändra sedan tillbaks ExecutionPolicy:n genom att köra Set-ExecutionPolicy till det ursprungliga värdet.


5. Fira att det fungerar

När man löser något drygt problem bör man alltid ta sig tid att fira detta meddelst kaffe & bulle eller annat gôtt.








Search Server Application - Access Denied

Hade ett problem nyligen där jag inte fick åtkomst till Search Server Application-gränssnittet (via SharePoint Central Admin) på en Search Server 2010 Express.













Det visar sig att det är en liten trilskande webpart "Shortcuts" på startsidan för sökadministrationen som medför detta. Den visar tydligen länkar som jag inte får se. Så att stänga denna gör alltså susen. Dock hade jag inte tillgång till något konto på servern som faktiskt kunde komma åt sidan, så jag började kika på att göra det programmatiskt. Jag valde en powershell implementation:


$site = new-Object Microsoft.SharePoint.SPSite("http://path-till-central-admin:1234/")
$web = $site.OpenWeb()
$page = $web.Url + "/searchadministration.aspx"
$webpartmanager = $web.GetLimitedWebPartManager($page, [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)
for($i=0;$i -lt $webpartmanager.WebParts.Count;$i++)
{
    if ($webpartmanager.WebParts[$i].title -eq "Shortcuts")
    {
       $webpartmanager.DeleteWebPart($webpartmanager.Webparts[$webpartmanager.WebParts[$i].ID])
    }
}
$web.Update();
$site.Dispose();

Spara detta som en .ps1-fil och kör scriptet via "SharePoint 2010 Management Shell" och åtkomst fungerar!

Gött!



tisdag 23 oktober 2012

RsStreamNotFound i SharePoint 2010 Report Viewer webpart

Hej!

Hade nyligen ett drygt problem i en Reporting Services integrerad SharePoint webpart där alla rapporter som skulle visas med "Asynchronous Rendering" flaggan satt till false inte ville låta sig renderas.









När jag inte hade denna flagga fick jag följande felmeddelande:
"Error, The stream cannot be found. The stream identifier that is provided to an operation cannot be located in the report server database. (rsStreamNotFound)"









Googlade lite och de flesta ville ha det relaterat till embeddade bilder i rapporten, samt att det skulle gå att lösa med UseCredentialCache satt till "false" i RS-databasen. Hur som helst hade jag inga bilder i min rapport och letade vidare...

https://connect.microsoft.com/SQLServer/feedback/details/262600/rsstreamnotfound-error-using-the-reportviewer-control

I lösningen i länken ovan nämns att de identifierat felet till att vara "att rapporten innehöll blanksteg i namnet"... då gick det upp för mig... rapporterna som inte fungerade "synkront" innehöll namn med Å, Ä och Ö.

Så... dumpa de vackra svenska vokalerna och undvik antagligen alla andra tecken som i olika XHR-requests antagligen översätts till nåt encodat värde.

Gött mos på Heden som de säger där nere i Göteborg!

måndag 15 oktober 2012

Inline-script i editor på EPiServer CMS 5

Hej!

Stötte nyligen på ett problem där en redaktör ville kunna lyfta in tredjeparts javascript i en editor i EPiServer CMS 5 R2 via html-läget.

Tydligen så verkar det fungera att göra det, det sparas ned korrekt till sidan. Dock försvinner alla inmatade värden i Editorn när redaktören redigerar egenskapen på nytt.

För att gå runt detta kan man mata in följande, t.ex.:
<P>&nbsp;

<SCRIPT src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></SCRIPT></P>

Så stannar det kvar nästa gång man editerar! Gött!

Obs. fulhetsdisclaimer: Den wrappade <p>-taggen är det EPiServer som lägger in och inte jag.

tisdag 2 oktober 2012

SharePoint 2010 och ram-minne

Hej!

Tänkte bara dela med mig av en insikt jag fick efter att ha installerat mer ram-minne på min utvecklar-burk idag...

Utvecklar du till SharePoint 2010 och har för lite ram-minne (<=8 GB)?
- Se till att skaffa mer ram-minne/ny dator! Motverkar gråhårighet och andra smärtor! Socialstyrelsen borde lagstifta kring detta snarast!

Over n out!

onsdag 12 september 2012

Visual Studio Tips & Trix vol 3

Fyller på med några till :)

1. TODO

Hör du till skaran utvecklare som använder sig av notationen:
//TODO: Fix this blabla

Hör du dessutom till den stackars skara utvecklare som fått i uppdrag att gå igenom alla TODOS?
Vad inte alla vet är att det finns ett inbyggt stöd för att navigera bland dessa kommentarer.

I Visual Studio, välj "View" -> "Task List". Välj "Comments" i dropdown-menyn, 
och där är alla TODOS i din solution. Navigera genom att välja en todo och dubbelklicka på den.











Gött!

2. Using alias

Ibland råkar man ut för att olika namespaces krockar namnsättningsmässigt, en vanlig lösning är då att man använder hela namespaces för ena eller båda. Men det går att sätta alias på sina using direktiv så att man kan särskilja dem utan att ange hela namespacen. Vilket jag tycker förbättrar läsbarheten.

Låt oss säga att vi har två klasser med namnet "Helper.cs", den ena under namespacet: MyLib.SharePoint och den andra under MyLib.EPiServer.

Då är det smidigt att ge den ena eller båda ett alias, t.ex.:
using EPi = MyLib.EPiServer;
using SP = MyLib.SharePoint;

Sen anropas respektive klass med aliaset som prefix/namespace EPi.Helper & SP.Helper


3. Bättre prestanda

Det finns många saker man kan göra som snabbar upp VS2010 betänkligt.

Nöjer mig med att plocka med en:

Stäng av HTML designern. 
Görs via Tools -> Options -> HTML Designer och se till att "Enable HTML designer" är urbockad. Jag använder aldrig denna funktion ändå (av principskäl sedan jag råkade ut för automatgenererad markup från helvetet).

Tänk bara på att inte plocka bort  features som du behöver/har nytta av :)

Länktips:
http://stackoverflow.com/questions/4325630/ways-to-speedup-visual-studio-2010
http://lennybacon.com/post/2010/10/18/UltimateGuideToSpeedUpVisualStudio

fredag 7 september 2012

Utveckla för Sharepoint 2010 utan att ha Sharepoint 2010 installerat

Hej å hå!

Idag fortsätter jag med lite mer Sharepoint. När man utvecklar för SharePoint är det gött att använda en virtuell maskin för att slippa lusa ner sitt host-OS med massa dret.

Tyvärr är det en pre-requisite för SharePoint 2010-utveckling i Visual Studio 2010 att man har just Sharepoint installerat.










Visst finns det stora fördelar med att ha en SharePoint installerad lokalt i utvecklingsmiljön (enkelt att debugga, automatisk deploy av solution etc.), men också gigantiska nackdelar i form av prestandatappet som kommer med prestandakraven för en Windows Server med SP 2010 installerat. Sitter man inte på en Rolls Royce till dator så kommer du garanterat att få möe tid över till att lägga patiens medan det står och tuggar.

Nåväl efter lite googlande hittade jag följande forum-tråd:
http://social.technet.microsoft.com/Forums/en/sharepoint2010programming/thread/cda807f6-4edf-4efc-8e9b-4d446356c8ae

Det intressanta är inlägget:
"Hmm .. just tried something .. exported the following hive from the SP2010 server: [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\14.0]
and installed it on my host. I can now create SP2010 projects in VS2010."

Nåväl, det testar vi!

1. Visual Studio 2010 SharePoint Power Tools

Först och främst behöver vi installera Visual Studio 2010 SharePoint Power Tools i vår utvecklingsmiljö, som återfinns här:

Det innehåller olika Sharepoint-templates som tar hand om allt tråkigt som man förr fick göra manuellt (wsp-builder, manifesthantering och annat zzzz).

Om man försöker använda någon SharePoint-template direkt efter installation av ovanstående så får man det felmeddelande som bilden överst visar. Så vi går vidare till register-hacket.

2. Regedit 

Leta upp nån maskin med SharePoint 2010 installerat och hoppa in i den.
In i regedit (kör -> regedit), leta upp noden 14.0 (hela sökvägen återfinns i citatet ovan). Högerklicka och välj export.



















Spara filen på nåt klurigt ställe och kopiera över den till din utvecklingsmiljö utan SharePoint.

3. Utvecklingsmiljön

Inne i ditt utvecklingsmiljö, högerklicka återigen på filen och välj "Merge".










Svara ja på säkerhetsvarningen som dyker upp och nu ska det vara klart.

4. Visual Studio 2010

Nu testar jag att skapa ett nytt projekt, t.ex. ett "Visual Web Part Project".

Jajamen, det funka fint! Peka ut nån dummy-url som din SharePoint-path.

Dock kommer olika SharePoint-dll-referenser inte att fungera, så du får kopiera över dessa (återfinns på en Sharepoint-server i mappen <sökväg till hive>\ISAPI\) och lägga t.ex. i en "Dependencies"-folder och peka ut dem därifrån för att kunna kompilera. Typ nåt sånt här:
















Sen är det bara att använda "Package"-funktionen så skapas .wsp-solutions filen i projektets \bin\<debug/release>. Sedan är det bara att flytta .wsp-filen till en SharePoint-server och installera den med stsadm el. dylikt.
Ps. för den som vill prova remote debugging så nämns även det i samma tråd.

måndag 27 augusti 2012

Programmatiskt skapa ett timestamp field i en sharepointlista

Då var det dags för lite Sharepoint. Inte min favorit-kopp té. Men ibland får man offra sig.

Fick i uppdrag att skapa en lista programmatiskt som innehåller bl.a. ett timestamp-fält. 

Eftersom jag hittat ytterst sparsamt med dokumentation och exempel kring hur man gör detta med kod så bestämde jag mig för att göra det omvända, dvs. skapa upp allt via GUI och sedan debugga och accessa fältet för att vad Sharepoint gjorde.

Det visar sig att man gör som här:
  • Sharepoint tillhandahåller alltid ett dolt fält "Created". Vill man lösa det snabbt kan man bara ändra egenskaper på detta fält så att det visas (toggla SPField-egenskapen ShowInDisplayForm), alt. lägga till det i önskad vy.
  • Det går att skapa ett s.k. "SPFieldCalculated" som läser upp innehållet från "Created" och formatterar det som ett datum.
För det senare fallet så gör man nåt i stil med:

SPSecurity.RunWithElevatedPrivileges(delegate()
{
    using (SPSite site = new SPSite(siteUrl))
    {
        using (SPWeb web = site.RootWeb)
        {
            SPList list = web.Lists["test"];
            string fieldName = list.Fields.Add("Timestamptest", SPFieldType.Calculated, false);
            SPFieldCalculated field = list.Fields[fieldName] as SPFieldCalculated;
            field.Formula = "=Created";
            field.OutputType = SPFieldType.DateTime;
            field.ShowInEditForm = false;
            field.Update();
            list.Update();
            SPView defaultView = list.DefaultView;
            defaultView.ViewFields.Add(field);
            defaultView.Update();

        }
    }
});

Testar:







Et voila! En timestamp kolumn som pekar på "Created". Gött!