Monday, February 25, 2013

Compare dynamic arrays with Powershell and some SharePoint

Today was a day I got to work on a revision to a script that I thought was working but apparently was not tested as well as I thought, not uncommon with pretty much everything I have done thus far. :) This is pretty well commented. I am starting to make use of templates when working with scripts to keep them looking a bit easier to understand.


## DESCRIPTION
## ------------------------------------------------
## Script to loop through a set of Subsites and a 
## SharePoint List of items and then either
## create a new item or update it in your SharePoint List.
## 
## EXPLAINATION
## ------------------------------------------------
## This example illustrates how to compare 2 dynamically 
## created arrays, look for the difference between
## the two, then do something like update a SharePoint list, 
## this could also apply to updating a text file
## or csv/excel file
##
## BEGIN
## ------------------------------------------------
## Load up the snapin so Powershell can work with SharePoint
## if the snapin was already loaded don't alert the user
Add-PSSnapin microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
## Clear any errors
CLS
## Set variables
## This variable will be the site where all the subsites 
## are that I want to get an array of
$parentSite = "http://sp2010/clients"
$rollupSite = "http://sp2010"
## Define the name of the SharePoint List we want to work with
$rollupListName = "Orders"
## Initialize and create empty arrays
$listOfSites = @()
$listOfItems = @()

function GetListOfSites
{
 ## Create a variable to hold the SharePoint site, invoke the connection
 ## to the site we defined in the $parentSite variable
 $web = Get-SPWeb $parentSite
 ## Check to make sure that url exists
 if($web -ne $true)
 {
  ## Loop through all the sub sites of the site url we got
  foreach($webSite in $web.Webs)
  {
   ## We have to use @() around the items we are going
   ## to add to the array. The += means we want to 
   ## keep adding items into our array named $listOfSites
   $listOfSites += @($webSite.Title)
  }
  ## Calling a function inside another function will allow you to make
  ## use of variables defined in functions that call it. Normally variable
  ## values are only accessible in the scope they were created. We need
  ## to pass off $listOfSites to the function GetLIstOfItems so it can process 
  ## the array
  GetListOfItems
 }
 else
 {
  ## Gracefully let the user know that the site they requested
  ## was not available or was not found.
  Write-Host "$web not found, check the url and try again."
 }
}

function GetListOfItems
{
 $listWeb = Get-SPWeb $rollupSite
 $listName = $listWeb.Lists[$rollupListName]
 foreach($rollupListItem in $listName.Items)
 {
  $listOfItems += @($rollupListItem.Title)
 }
 CompareItems
}

function CompareItems
{
 foreach($foundSite in $listOfSites)
 {
  ## Filter out anything that already exists in the Rollup List
  ## We have to use -notcontains/-contains when analyizng arrays
  if($listOfItems -notcontains $foundSite)
  {
   ## Item not found so lets create new item
   foreach($value in $foundSite)
   {
    $rollupListWeb = Get-SPWeb $rollupSite
    $rollupList = $rollupListWeb.Lists[$rollupListName]
    $rollupListItems = $rollupList.Items.Add()
    $rollupListItems["Title"] = $value
    $rollupListItems.Update()
    Write-Host "Creating $value"
   }
  }
  else
  {
   ## Item was found so lets update it
   foreach($value in $foundSite)
   {
    $rollupListWeb = Get-SPWeb $rollupSite
    $rollupList = $rollupListWeb.Lists[$rollupListName]
    $rollupListItems = $rollupList.Items
    
    if($rollupListItems.Title  -notmatch $value)
    {
     $rollupListItem["Title"] = $value
     $rollupListItem.Update()
     Write-Host "Updating $value"
    }
   } 
  }
 }
}
Start-SPAssignment -Global
GetListOfSites
Stop-SPAssignment -Global

Thursday, February 21, 2013

SharePoint, cookies + masterpages

Today I find my self tasked to take care of a requirement on our network to alert users that they are accessing sensitive information and that in order to access our portal we need to make sure the message pops up no matter if they have book marked their sites or have shortcuts to other locations on the portal that they use to get into their site.

Normally I would create a Visual Studio solution for a custom master page, but our portal was migrated from 2007 to 2010 and some site collections had already been using a customized master page that in most cases had been modified using SharePoint Designer and those changes were not going to be changed.

The other challenge I had was I needed a way to find out what sites had a different master page than the custom one that was in use at the top level site,  luckily we only had 3 different master pages in our farm.

Approach to resolving the problem.

Session cookie
In order to get our pop up to display on our intranet we decided to use a cookie that would be called from the master page, this script was uploaded into our main portal site and put in the document library, Site Assets, and we made sure that ad\domain users had read access to the library. Since SharePoint 2010 supports javascript out of the box this should be a quick fix.

Once this was done we can use the same line of code added to all of the custom master pages to reference our script, so if the message ever needed to be updated a user could go to the library and edit the javascript file instead of waiting for a developer and a trip through our change management process just to update some text.

The last requirement was that the pop up should needed to be based on the session for the current user, once the user closed their browser the cookie was destroyed, which means that the next time the user opened their browser and went to our site they would be prompted again to acknowledge that they were accessing sensitive information, this needed to be seamless, meaning that when the users navigated from site collection to site collection they should never see the prompt, until they closed and reopened their browser.

var bannerText = "No physical clone trooper costume was produced for their first onscreen appearance. \n\n As revealed in the audio commentary of Star Wars Episode II: Attack of the Clones, every single clone trooper in the film is a computer generated image. In fact, the only thing present on the set was the actor, whose head was used during scenes requiring a clone's face. In Star Wars Episode III: Revenge of the Sith, the armor is redesigned, but remains computer generated. Within the fictional history of the Star Wars universe, this is the basis of the Imperial Stormtrooper armor and shows a gradual evolution in design toward the armor worn in episodes III-VI, known as "Phase II" armor.\n\n The actual armor never changes, but the helmet progresses.";

function setCookie(name,value,days) {
       if (days) {
          var date = new Date();
          date.setTime(date.getTime()+(days*24*60*60*1000));
          var expires = ""; expires="+date.toGMTString();
       }
       else var expires = "";
       document.cookie = name + "=" + value + expires + "; path=/; domain=.company.com";
    }

function getCookie(name) {
       var nameEQ = name + "=";
       var ca = document.cookie.split(';');
       for(var i=0;i < ca.length;i++) {
          var c = ca[i];
          while (c.charAt(0)==' ') c = c.substring(1,c.length);
          if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
       }
       return null;
    }

function CheckAuth() 
{
/* Check to see if the AuthConsent cookie exists and put it into a variable so we can check to see if it found what we were looking for*/
 var userConsents = getCookie('AuthConsent');
/* The cookie was found, lets see if the value of true has been added to the file */
    if (userConsents != 'true') {
/* The cookie file was found but the value of true was not in it, so show the warning banner to the user */
        var haveConsent = confirm(bannerText);
/* The user clicked the OK button so now add the cookie */
        if (haveConsent) {
/* Here we set the cookie name in the first parameter, then the value inside the cookie file which we used the word true for, it could be any unique value, and last we set the number of days we want the cookie to set for, we say 0 as we want the cookie to be destroyed when the user closes their browser. */
            setCookie('AuthConsent', 'true', 0);
        }
        else {
            window.navigate("denied.html");
        }
    }
}
CheckAuth()
Updating the master page file
First make sure you know which master page your site is using, either by using Powershell or going to the portal and going to Site Actions > Site Settings > under Look and Feel > Master page. (You really should use Powershell to find out if any of your sites have a different master page.) Also please note that if you do not see the Master page link highlighted below you will need to go to Site Actions> Manage Site Features and enable SharePoint Publishing, IF YOU GO THROUGH THE UI, otherwise use Powershell :)
Open SharePoint Designer and check out the current master page and then Right click on the master page and Edit in Advanced Mode so we can add our little one liner in between the head tags, normally I find the closing tag and add my script reference just above it.
Once this has been added then we need to save, check in, and approve the master page so all of our users can start using it and getting that wonderful prompt. Once you check it in and then accept the dialog window to go to SharePoint and Approve the page, you should start seeing the prompt as the page loads up. In this instance only people with access to approve and higher should be able to see this change, once you Approve it all users will see the change.

Powershell script
Since the master page was customized and set differently in various sites and site collections, I needed a way to update all the sites in a site collection where they were not using the same master page, for this we will use Powershell to loop through all of our sites and set the master page.

if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) 
{
    Add-PSSnapin "Microsoft.SharePoint.PowerShell"
}
CLS
## Set variables
$url = "https://www.company.com/"
$masterpage = "$url/_catalogs/masterpage/Intranet.IT.master"
## Script
Start-SPAssignment -Global
$site = Get-SPWeb $url
if($site)
{
    foreach($web in $webs.Webs)
    {
        $web.custommasterurl = $masterpage
        $web.masterUrl = $masterpage 
        $web.Update()
        $web.dispose()
    }
}
else
{
    write-host "Site not found, please check the URL of the site"
}

Stop-SPAssignment -Global

Tuesday, February 12, 2013

Using Powershell and log files

So much of my initial learning of Powershell involved just banging out scripts and using trial and error to get going. Most of my understanding of what was happening was reading the console, which some nights were hours of going around in circles making a change, run it, make a change, run it.... In hindsight I should have first figured out how to log any data getting output, including errors.
This PowerShell script is my first attempt at getting some details about a set of sub sites in SharePoint and writing them to a text file and then opening the text file to screen so you can see the results.

If anything it has been a great learning experience.

add-pssnapin microsoft.sharepoint.powershell
CLS
## Set Variables
$site = "http://sp2010/dashboards"
$logFilePath = "c:\Temp\Powershell\Logs\sites.txt"
## Delete the file if it exists so the data does not append to the file
$deleteLogFile = Remove-Item -Path $logFilePath -Confirm:$false
## Create a function to handle the logic
function GetListOfSites
{
 ## Check to see if the log file exists
 $checkForLogFile = Test-Path $logFilePath
 if($checkForLogFile -ne $true)
 {
  $webs = Get-SPWeb $site
  ## Make sure the site we specified exists
  if($webs)
   {
    foreach($web in $webs.Webs)
    {
     ## Set the values to variables so we can format the output
     $webName = $web.Name
     $webDescription = $web.Description
     $webUrl = $web.Url
     $webID = $web.ID
     $webTemplate= $web.webtemplate
     $webTemplateID = $web.webtemplateID
     ## Getting fancy
     $siteDetails += 
      "---------SITE DETAILS------------",
      "Site: $webName",
      "Description:$webDescription",
      "Site Url:$webUrl",
      "Site ID:$webID",
      "Site Template:$webTemplate#$webTemplateID"
    }
    ## Output the values into a text file and save it to the file system
    $siteDetails  > $logFilePath 
    Write-Host "Log file has been generated, please wait a moment while the file opens..."
   }
  else
  {
   Write-Host "Site: $web not found, exiting."
  }
  ## Open the log file 
  Invoke-Item $logFilePath
 }
 else
 {
  Write-Host "Log file: $logFilePath not found, exiting."
 }
}
### MAIN
## Using Start-SPAssignment -Global to ensure that we don't leave a bunch of garbage in memory
Start-SPAssignment -Global
GetListOfSites
Stop-SPAssignment -Global

Thursday, January 31, 2013

PowerGUI and SharePoint

I recently upgraded my trusty copy of PowerGUI and then went about my buisness, until I tried to edit a Powershell SharePoint script, which I knew was working. As I tried to execute the script I was seeing an error
 “Microsoft SharePoint is not supported with version 4.0.30319.261 of the Microsoft .Net Runtime.”

Hmmm, closed PowerGUI and opened it back up same error, hmmmm. Head back over to the PowerGUI site and started to look around and found that based on the release notes of PowerGUI 3.2. it seems they added .NET 4.0 support natively. If you want to continue to run you SharePoint 2010 2.0 cmdlets in PowerGUI, you will have to update your config file.
This change works with x64 bit setups, if you want to use a x32 bit then you just need to change the path

Good practices say you should make a backup copy of this file
C:\Program Files (x86)\PowerGUI\ScriptEditor.exe.config

Once that is backed up lets start off by right clicking on Notepad and run it as an Administrator
As the file opens you should see a line of code

supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"

just be sure to wrap html comment tags around this line, save it and relaunch PowerGUI and you should be all set.




Access denied after adding SSRS webpart to SharePoint

We have our portal set up so that we can serve external customers as well as our intranet users, we are also using SSRS in SharePoint integrated mode. After adding an SSRS report webpart to our 500 or so dashboard sites, we started getting calls from our external customers saying they were getting access denied when they tried to access the dashboard pages. 3 of us spent several days of combing through error logs, adjusting SharePoint group permissions, restarting the Profile Import Service, restarting IIS, all to no avail.

What happened? Once I had finished creating the report and uploading it to the report library in SharePoint, I had not gone into the SharePoint library where the reports were, mouse over the report to get the sub-menu, and selected  Publish a major version of the report, so while our internal users are having no issues seeing the report our external users were not able to get there, but once that was done users were able to get to their dashboard pages without issues.

What made this issue hard to troubleshoot was the fact that we had several errors in our log files but nothing was saying access denied for this specific webpart, we started taking web parts off the page one at a time in order to see if we could pin point this to a specific web part.

If you are using SSRS and SharePoint be sure to PUBLISH YOUR SSRS REPORTS!!! :)

Monday, October 22, 2012

Removing a web part from a page recursively with Powershell

This week we were making some changes to the content on our orders pages, we decided to remove a web part that was showing some static content and replace it with an SSRS report that I blogged about earlier. I needed this to quickly remove a web part off of each of my 500+ orders sites, so Powershell was looking like the tool of choice for this operation, and let me just take a moment to say I am stoked that at last I am spending more time in Powershell to do even simple things that are now too slow going through the SharePoint UI, removing or adding web parts via Powershell is an awesome way to do it. I was able to pick at a few samples I found online and create something that worked for me.

 

Friday, October 19, 2012

Using Powershell to add a file from a shared drive to a SharePoint Document Library

One of our older systems was outputting reports to a shared folder on our network, we needed an automated way to grab the file and add it to a SharePoint Document Library. So time to break out the Powershell ISE and see if we can do this with out breaking a sweat. First make sure the service account has access to both the network share and the SharePoint Document Library, otherwise you will spend a bit of time trying to troubleshoot your script. The script is pretty well commented so no need to further explain.