Tuesday, 10 December 2013

Remove Custom Actions


<#
.Synopsis
   Deletes Custom Site Actions
.DESCRIPTION
       iterates over selected site or all sites, lists all custom site actions; allows user to select custom actions for deletion
.EXAMPLE
   Remove-spmSiteAction
.EXAMPLE
   Remove-spmSiteAction -$siteAppUrl http://wingtip.ca
#>
function Remove-spmSiteAction
{
    [CmdletBinding()]
    [OutputType([int])]
    Param
    (
        # Param1 help description
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$True,
                   Position=0)]
        $siteAppUrl
    )

    Begin
    {
              #if site app url is not proviede as a parameter, prompt the user to pick one from the current farm
              if($siteAppUrl -eq $null -or $siteAppUrl -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 Site 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){
                           $siteAppUrl = $Apps
                     }
                     else{
                           $siteAppUrl = $Apps[$selection]
                     }
              }
             
             
    }
    Process
    {
              $siteAppUrl | ForEach{
                     $site = Get-SPSite $_
                     $SiteActionsToRemove = @()
                    
                     if($SiteActionsToDelete.count -eq $null)
                     {
                           $selection
                           do
                           {
                                  Write-host "Please select the Custom action you wish to remove, 0 to remove selected entries, -1 to cancel"
                                  $count = 0
                                  $CustomSiteActionList = @()
                                  foreach($customAction in $site.UserCustomActions)
                                  {
                                         if(-not ($SiteActionsToRemove -contains $customAction.Id))
                                         {
                                                Write-Host (++$count) $customAction.Title, $customAction.Id
                                                $CustomSiteActionList += $customAction.Id
                                         }
                                  }
                                 
                                  $selection = Read-Host
                                 
                                  if(($selection -eq -1))
                                  {
                                         write-error ("Terminated cmdlet, user request") -erroraction stop
                                  }
                                  elseif($selection -gt $count)
                                  {
                                         Write-host "`nselection too high, please pick again "
                                  }
                                  elseif($selection -lt 0)
                                  {
                                         Write-host "`nselection too low, please pick again"
                                  }
                                  elseif($selection -gt 0)
                                  {
                                         $SiteActionsToRemove += $CustomSiteActionList[$selection-1]
                                  }
                           }while($selection -ne 0)
                          
                     }
                     Write-Host "`n"
                     if($SiteActionsToRemove -eq $null)
                     {
                           Write-Host No Site actions selected for deletion
                     }
                     else
                      {
                           foreach($customActionGuid in $SiteActionsToRemove)
                           {
                                  $customAction = $site.UserCustomActions[$customActionGuid]
                                  Write-host Deleteing Custom Action: $customAction.title With GUID: $customAction.id
                                  $customAction.Delete()
                           }
                     }
                     $site.dispose()
              }
    }
    End
    {
                    
    }

}

Tuesday, 19 November 2013

Property bag

It's basically a hashtable in SharePoint, it stores key - value pair that you can access in your code behind; you can install the following wsp from codeplex to view it from your site settings.

Property bag

once downloaded run this powershell to install it

Add-SPSolution -LiteralPath C:\Users\YOURUSER\Downloads\PropertyBagSettings2010.wsp

Install-SPSolution -Identity PropertyBagSettings2010.wsp -GACDeployment

with your property bag installed navigate to your site setting and notice that under Site Administration there's a new link Property bag settings

when clicked on, this will let you view, add, edit, and remove different entries, try not to wreck anything.

now this is where we like to connection strings, generally we add them on feature activating
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    using (SPWeb currentWeb = ((SPSite)properties.Feature.Parent).RootWeb)
    {
        if (!currentWeb.AllProperties.ContainsKey("spm_ConnectionString"))
        {
            currentWeb.AllProperties["spm_ConnectionString"] = SPM_CONNECTIONSTRING;
            currentWeb.Update();
            currentWeb.Properties.Update();
        }
    }

}
and remove them on feature deactivating
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
    using (SPWeb currentWeb = ((SPSite)properties.Feature.Parent).RootWeb)
    {
        if (currentWeb.AllProperties.ContainsKey("spm_ConnectionString"))
        {
            currentWeb.AllProperties.Remove("spm_ConnectionString");
            currentWeb.Update();
            currentWeb.Properties.Update();
        }
    }

}

now keep in mind that SPM_CONNECTIONSTRING is just a constant string that as the name insinuates is a connection string.

Friday, 15 November 2013

Canadian Sin Regex

Regex is a two fold problem:

  • the first is that you have a problem
  • the second is that your problem can be solved using regex

What do I mean by this? well regex is one of those things that you're not going to use very often so it's not really worth the effort to learn it 100% but just enough to get what you need done and forget it by the time you need it again, much like calculus.

Anyway this is a pretty solid site to find what you need Regex Library.

but this is what I use to verify a Canadian sin "^\d{3}(\s?|[-]?)\d{3}(\s?|[-]?)\d{3}$"

It's got you covered for 123123123, 123 123 123, 123-123-123

to use it with an input box use a combination of the pattern and title attributes

<input id = "SIN_TXT" name="SIN_TXT" type ="text" title="6 digit Canadain Social Insurence Number"  pattern="^\d{3}(\s?|[-]?)\d{3}(\s?|[-]?)\d{3}$" placeholder="### ### ###" />

if you are a poor soul that has to support a lower end browser such as ie8 then attach it using javascript

function validateSIN(sinString)
{
    var sinPattern = /^\d{3}(\s?|[-]?)\d{3}(\s?|[-]?)\d{3}$/;
    return sinPattern.test(sinString);
}

Now that you've validated that the user is submitting a 9 digit number, lets confirm that it is in fact a SIN and not just 9 random digits, check out this Validate Sin Number.

here's the algorithm I use:

function verifySIN(SIN) {
    var sinArray = SIN.replace(/\W/g, '')
    var t = 0;
    if (sinArray.length != 9)
        return false;

    for (x in sinArray) {
        var p = 0;
        t += (p = (x % 2 + 1) * sinArray[x]) > 9 ? (p + (p % 10 * 9)) / 10 : p;
    }

    return t % 10 === 0;
}

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.