Thursday, 31 October 2013

All features must have unique directories to avoid overwriting files error

The solution cannot be deployed.  Directory "*" associated with feature '*' in the solution is used by feature '*' installed in the farm. All features must have unique directories to avoid overwriting files.

This was awful the following in powershell worked for me

Clear-Host
$features = Get-SPFeature –Limit ALL | Where-Object {$_.DisplayName -like "ccw*"}

foreach($f in $features)
{
Write-Host $f.Id $f.Farm $f.DisplayName
$f.Delete()

}

my features had no scope and weren't showing up through central admin

Saturday, 19 October 2013

FeatureUninstalling Context

You want to grab the Site or Web that your feature was installed on to do some last minute clean up before the package is retracted from your site; well me friend too bad. Don't believe me? paste this code into your Feature Receiver.
public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
    using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\output.txt", true))
    {
        file.WriteLine("Feature Uninstalling {0}", DateTime.Now.ToString());
       
        if (properties.Feature == null)
            file.WriteLine("FML");
        else
            file.WriteLine("Not FML");
    }
}
now go check your c:\output.txt file; what's it say? oh that's right, FML, or I should say FYL, meaning that:
using (SPWeb web = properties.Feature.Parent as SPWeb){} or
using (SPSite site = properties.Feature.Parent as SPSite){}
is useless, so how do we deal with this? 

To make my goal clear: what I'm trying to accomplish is delete list instances that were deployed through a feature in the package, as you may know when you retract your package the list instance(s) hang out, it's an artifact that gets left behind, . This is especially a problem for me because when the package is retracted so are the content types for my lists, leaving my lists useless, unopenable. 

So what do we do now? If you run the above experiment with "properties.Definition" you'll get much more satisfying results. Should you dive further you'll learn that you can grab the SPFarm object. Is this ideal? by no means, but you got to do, what you got to do.

Now you most likely have multiple environments: DEV, TEST, prePROD, and PROD, now you probably do not want to be refactoring your event receiver for each environment, so the dirty work around I use is:

public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
    string[] WebApplicationNames = { "Dev", "Test", "PreProd","Production" };
   
    SPFarm farm = properties.Definition.Farm;
   
    SPWebApplicationCollection webApps =
        farm.Services.GetValue<SPWebService>("").WebApplications; 
 
    foreach (SPWebApplication webApp in webApps)
    {
        if (Array.Exists(WebApplicationNames, delegate(string s)
            {return s.Equals(webApp.Name);}))
        {
            foreach (SPSite s in webApp.Sites)
            {
                using(SPWeb web = s.RootWeb)
                    DeleteListInstances(web, new string[] { "List Instance1 Name", "List Instance2 name" });   
                s.Dispose();
            }
        }
    }           
}

private void DeleteListInstances(SPWeb web, string[] listInstanceNames)
{
    foreach (string listInstanceName in listInstanceNames)
    {
        SPSecurity.RunWithElevatedPrivileges(delegate()
        {
            SPList list = web.Lists.TryGetList(listInstanceName);
            if (list != null)
                web.Lists.Delete(list.ID);
        });       
    }
    web.Dispose();
}

Now do I feel Dirty? you bet I do; so if you have a better way please feel free to let me know.


Wednesday, 16 October 2013

Remove Webpart from gallery

This is how you would programmatically remove a webpart from a webpart gallery in the feature event receiver.

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
    using (SPWeb currentWeb = ((SPSite)properties.Feature.Parent).RootWeb)
    {
        //remove Student lottery webpart from web part gallery
        SPList webPartList = currentWeb.Lists["Web Part Gallery"];

        foreach (SPListItem webpart in webPartList.Items)
        {
            if (webpart["Title"].ToString() == "WebpartTitle")
            {
                webPartList.Items.DeleteItemById(webpart.ID);
                break;
            }
        }

        webPartList.Update();
        currentWeb.Update();
    }

}

You can find the webpart title in the mywebpartRegistration.webpart file, look for an element property with an name attribute of title.

Wednesday, 4 September 2013

Restore Content DB from .bak

This is a powershell script to restore a content database on a Dev Box, make a note that in the restoreDB function you have to specify your path to the .mdf and .ldf files on your local Dev box

LoadSnapIn "Microsoft.Sharepoint.PowerShell"
LoadSnapIn "SQLServerProviderSnapin100"
LoadSnapIn "SQLServerCmdletSnapin100"
           
<#
.Synopsis
   CCW Function to locally replace SP database
.DESCRIPTION
   City of Windsor Fucntion used to replace a SharePoint Content Database with a previously created back up
.EXAMPLE
   Restore-ccwSpContentDB -DataBaseName Test_DB -DatabaseServer SSRAP1 -DBFilePath c:\Test_DB.bak
.EXAMPLE
   Restore-ccwSpContentDB -DataBaseName Test_DB -DatabaseServer SSRAP1 -DBFilePath c:\Test_DB.bak -SecondarySiteAdministrator windsor\pricer
.EXAMPLE
   Restore-ccwSpContentDB -DataBaseName Test_DB -DatabaseServer SSRAP1 -DBFilePath c:\Test_DB.bak -SecondarySiteAdministrator windsor\pricer -PrimarySiteAdministrator windsor\ciuciap
.INPUTS
   Inputs to this cmdlet (if any)
.OUTPUTS
   None
.NOTES
   General notes
.COMPONENT
   Pawel
.ROLE
   The role this cmdlet belongs to
.FUNCTIONALITY
   The functionality that best describes this cmdlet
#>
function Restore-ccwSpContentDB
{
    [CmdletBinding(DefaultParameterSetName='CommandLine',
                  ConfirmImpact='High')]
    Param
    (
        # XML file containing deploymnet
        [Parameter(Position=0, Mandatory=$true, ParameterSetName="XML")]
        [string] $xmlPackage,

        # Database name
        [Parameter(Position=0, Mandatory=$true, ParameterSetName="CommandLine")]
            [ValidateScript({$dbName = $_; Invoke-SQLCmd -Query "sp_databases" | ?{$_.DataBase_Name -eq $dbName}})]
        [string] $DataBaseName,

        # Backup file path
        [Parameter(Position=1, Mandatory=$true, ParameterSetName="CommandLine")]
            [ValidateScript({Test-Path $_ -pathtype leaf -Include "*.bak"})]
        [string] $DBFilePath,

            # Site Url to replace the database on with a backup
        [Parameter(Position=2, ParameterSetName="CommandLine")]
            [ValidateScript({Get-SPSite -Identity $_ | Select-Object -Property Exists -ErrorAction SilentlyContinue})]
        [string[]] $SiteUrl,
           
            # DatabaseServer
        [Parameter(Position=3, Mandatory=$true, ParameterSetName="CommandLine")]
            [ValidateScript({Test-Connection $_})]
        [String] $DatabaseServer,
           
        # Primary Site Admin (Domain/User)
        [Parameter(Position=4, ParameterSetName="CommandLine")]
            [ValidateNotNullOrEmpty()]
        $PrimarySiteAdministrator = $null,

        # Secondary Site Admin (Domian/User)
        [Parameter(Position=5, ParameterSetName="CommandLine")]
            [ValidateNotNullOrEmpty()]
        $SecondarySiteAdministrator = $null
    )

    Begin
    {
            Clear-Host
    
            [string[]] $spServices = "SPAdminV4", "SPTimerV4"
           
        #if site app url is not proviede as a parameter, prompt the user to pick one from the current farm
            if($SiteUrl -eq $null -or $SiteUrl -eq "")
            {
                  $myFarmServices = (Get-SPFarm).services | Where {$_.typename -like "*Web Application"}
                  $selection = -1
                  [string[]]$Apps = @()
                 
                  do{
                        $count = 0
                        foreach($webApp in $myFarmServices.WebApplications){
                              Write-output ("{0}) {1}" -f $count++, $webApp.url)
                              $Apps += $webApp.url
                        }
                        Write-output ("{0}) All" -f $count)
                        if(($selection = Read-Host "Please select the web app you'd like to deploy to, -1 to quit") -eq -1){
                              write-error ("Terminated cmdlet, user request") -erroraction stop
                        }
                  }while($selection -lt 0 -or $selection -gt $count)
                 
                  if($selection -eq $count){
                        $SiteUrl = $Apps
                  }
                  else{
                        $SiteUrl = $Apps[$selection]
                  }
            }
    }
    Process
    {
        if ($pscmdlet.ShouldProcess("dataBase $DataBase_Name for site $SiteUrl", "Restore"))
        {
                  Write-Progress -Activity "Restoring $newDBName" -Status "dismounting $newDBName from $SiteUrl" -PercentComplete 0
                  Get-SPContentDatabase $DataBaseName | Dismount-SPContentDatabase
                  Write-Progress -Activity "Restoring $newDBName" -Status "Stoping services: $spServices" -PercentComplete 20
                  Stop-Service $spServices
                  Write-Progress -Activity "Restoring $newDBName" -Status "Stoping IIS" -PercentComplete 30
                  iisReset -Stop
                 
                  Write-Progress -Activity "Restoring $newDBName" -Status "Restoring Database (this may take up to 10 min)" -PercentComplete 40
                  RestoreDB $DataBaseName $DBFilePath
                 
                  Start-Sleep -s 10
                 
                  Write-Progress -Activity "Restoring $newDBName" -Status "Starting IIS" -PercentComplete 75
                  iisReset -Start
                  Write-Progress -Activity "Restoring $newDBName" -Status "Starting services: $spServices" -PercentComplete 80
                  Start-Service $spServices
                 
                  Write-Progress -Activity "Restoring $newDBName" -Status "Mounting $DataBaseName on $SiteUrl" -PercentComplete 90
                  Mount-SPContentDatabase $DataBaseName -DatabaseServer $DatabaseServer -WebApplication "$SiteUrl"
                 
                 
                  if($PrimarySiteAdministrator -ne $null -or $SecondarySiteAdministrator -ne $null)
                  {
                        Write-Progress -Activity "Restoring $newDBName" -Status "Changing site admin(s)" -PercentComplete 95
                        ChangeSiteCollectionAdministrators $PrimarySiteAdministrator $SecondarySiteAdministrator $SiteUrl
                  }
                  Write-Progress -Activity "Restoring $newDBName" -Status "Complete!" -PercentComplete 100
        }
    }
    End
    {
    }
}

function LoadSnapIn($snapIn){
      if (!(Get-PSSnapin | ?{$_.name -eq $snapIn}))
      {
            if(Get-PSSnapin -registered | ?{$_.name -eq $snapIn})
            {
                  Add-PsSnapin $snapIn
                  Write-Output "$snapIn has been added to this session"
            }
            else
            {
                  write-output "$snapIn is not registered with the system."
                  break
            }
      }
      else
      {
            write-output "$snapIn has already been added to this session"
      } 
}

function get-SiteUrl([string]$webUrl){
    if($webUrl.StartsWith("https")){
        return $webUrl.Substring(0, $webUrl.IndexOf('/',8))
    }
    elseif($webUrl.StartsWith("http")){
        return $webUrl.Substring(0, $webUrl.IndexOf('/',7))
    }
}

function RestoreDB ([string] $newDBName, [string] $backupPath){
    [string] $dbCommand_SingleUser = "ALTER DATABASE $newDBName SET SINGLE_USER WITH Rollback Immediate "
      Invoke-Sqlcmd -Query $dbCommand_SingleUser

      [string] $dbCommand_FileListOnly = "RESTORE fileListOnly FROM disk = '$backupPath'"
                                                           
      Invoke-SQLCmd -Query $dbCommand_FileListOnly -ServerInstance "SSRAP1" -OutVariable t

      $NameData = ($t | where {$_.Type -eq "D"} | select LogicalName).LogicalName 
      $NameLog = ($t | where {$_.Type -eq "L"} | select LogicalName).LogicalName
     
      [string] $dbCommand_Restore = "RESTORE DATABASE $newDBName FROM DISK = '$backupPath' WITH MOVE '$NameData ' TO 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.INSTANCEID\MSSQL\DATA\$newDBName.mdf', MOVE '$NameLog ' TO 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.INSTANCEID\MSSQL\DATA\$newDBName.LDF', REPLACE"

      Invoke-Sqlcmd -Query $dbCommand_Restore  -querytimeout ([int]::MaxValue)
     
      [string] $dbCommand_MultiUser = "ALTER DATABASE $newDBName SET Multi_User"
                                                       
      Invoke-Sqlcmd -Query $dbCommand_MultiUser 
}

function ChangeSiteCollectionAdministrators([string] $primarySiteAdmin, [string] $secondarySiteAdmin, [string] $siteUrl){
    Write-output "Changing the site collection administrators "
   
      $site = Get-SPSite $siteUrl
     
    If ($site -ne $null)
    {
            if($PrimarySiteAdministrator -ne $null -and $SecondarySiteAdministrator -ne $null)
            {
            Set-SPSite -Identity $site.Url -OwnerAlias $PrimarySiteAdministrator -SecondaryOwnerAlias $SecondarySiteAdministrator
            Write-Output "Primary Administrators for '$site' is $PrimarySiteAdministrator"
                  Write-Output "Secondary Administrators for '$site' is $SecondarySiteAdministrator"
            }
            elseif($PrimarySiteAdministrator -ne $null)
            {
                  Set-SPSite -Identity $site.Url -OwnerAlias $PrimarySiteAdministrator
            Write-Output "Primary Administrators for '$site' is $PrimarySiteAdministrator"
            }
            elseif($SecondarySiteAdministrator -ne $null)
            {
                  Set-SPSite -Identity $site.Url -SecondaryOwnerAlias $SecondarySiteAdministrator
            Write-Output "Secondary Administrators for '$site' is $SecondarySiteAdministrator"
            }
    }
     
      $site.Dispose()
}