Tuesday, January 26, 2016

Fast way to find mixed http content on https sites

When your site is accessed through https and have content which is loaded using http protocol (e.g. images, iframes or javascript files) you will have either this content blocked or will see warning, e.g. like in IE “Only secure content is displayed”:

Fastest way to find exact resource which caused this issue is to use Developer tools available in all modern browsers (by default click F12). In console tab you will see what caused this problem:

IE:

FF:

Chrome:

Sunday, January 24, 2016

Add custom javascript file to all pages in Sharepoint Online site collection

In my previous post (see Add custom javascript file to all pages in on-premise Sharepoint site collection without modifying masterpage) I showed how to inject custom javascript file to all pages on all sites in all site collections of specified web application without modifying master page. In this post I will show how to do the same using client object model for Sharepoint Online. For enumerating all site collections in Sharepoint Online we will use technique described in my other post Enumerate all tenant’s site collections in Sharepoint Online via PowerShell. Here is PowerShell which adds custom javascript to all site collections on Sharepoint Online:

   1: param(
   2:     [string]$adminWebAppUrl,
   3:     [string]$login,
   4:     [string]$password
   5: )
   6:  
   7: $currentDir = Convert-Path(Get-Location)
   8: $dllsDir = resolve-path($currentDir + "\..\dlls")
   9:  
  10: [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllsDir,
  11:     "Microsoft.SharePoint.Client.dll"))
  12: [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllsDir,
  13:     "Microsoft.SharePoint.Client.Runtime.dll"))
  14: [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllsDir,
  15:     "Microsoft.SharePoint.Client.Taxonomy.dll"))
  16: [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllsDir,
  17:     "Microsoft.Online.SharePoint.Client.Tenant.dll"))
  18:  
  19: Import-Module ..\spps\spps.psm1 
  20: Import-Module ..\spps\spps.subsites.psm1
  21:  
  22: if (-not $adminWebAppUrl)
  23: {
  24:     Write-Host "Specify admin web app url in adminWebAppUrl parameter" -foregroundcolor red
  25:     return
  26: }
  27:  
  28: if (-not $login)
  29: {
  30:     Write-Host "Specify user name in login parameter" -foregroundcolor red
  31:     return
  32: }
  33:  
  34: if (-not $password)
  35: {
  36:     Write-Host "Specify user password in password parameter" -foregroundcolor red
  37:     return
  38: }
  39:  
  40: function Upload-Script($ctx, $web)
  41: {
  42:     Write-Host "Upload script to Site Assets" -foregroundColor Green
  43:     $ctx.Load($web.Lists)
  44:     $ctx.ExecuteQuery()
  45:  
  46:     $assetsLib = $null
  47:     foreach ($l in $web.Lists)
  48:     {
  49:         if ($l.Title -eq "Site Assets")
  50:         {
  51:             $assetsLib = $l
  52:             break
  53:         }
  54:     }
  55:  
  56:     if (-not $assetsLib)
  57:     {
  58:         Write-Host "Sites Assets doclib not found. Script won't be uploaded" -foregroundcolor red
  59:         return
  60:     }
  61:  
  62:     $fileName = "foo.js"
  63:     $filePath = [System.IO.Path]::Combine($currentDir, $fileName)
  64:     if (-not [System.IO.File]::Exists($filePath))
  65:     {
  66:         Write-Host "File $filePath doesn't exist. It won't be uploaded" -foregroundcolor red
  67:         return
  68:     }
  69:  
  70:     $newFile = New-Object "Microsoft.SharePoint.Client.FileCreationInformation"
  71:     $newFile.Content = [System.IO.File]::ReadAllBytes($filePath)
  72:     $newFile.Url = $fileName
  73:     $newFile.Overwrite = $true
  74:     $uploadFile = $assetsLib.RootFolder.Files.Add($newFile)
  75:  
  76:     $ctx.Load($uploadFile)
  77:     $ctx.ExecuteQuery()
  78: }
  79:  
  80: function Add-Link($ctx, $web, $actionUrl, $sequence)
  81: {
  82:     Write-Host "Add custom action link" -foregroundColor Green
  83:     $existingActions = $web.UserCustomActions
  84:     $ctx.Load($existingActions)
  85:     $ctx.ExecuteQuery()
  86:  
  87:     $exists = $false
  88:     foreach($ac in $existingActions)
  89:     {
  90:         if ($ac.ScriptSrc.ToLower() -eq $actionUrl)
  91:         {
  92:             $exists = $true
  93:             break
  94:         }
  95:     }
  96:     
  97:     if ($exists)
  98:     {
  99:         Write-Host "Custom action is already added" -foregroundcolor Yellow
 100:         return
 101:     }
 102:  
 103:     $gaScript = $existingActions.Add()
 104:     $gaScript.Location = "ScriptLink"
 105:     $gaScript.ScriptSrc = $actionUrl
 106:     $gaScript.Sequence = $sequence
 107:     $gaScript.Update()
 108:  
 109:     $ctx.ExecuteQuery()
 110: }
 111:  
 112: function Add-Custom-Action($url)
 113: {
 114:     Write-Host "Adding custom action to $url" -foregroundColor Green
 115:     $clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($url)    
 116:     $clientContext.RequestTimeOut = 1000 * 60 * 10;
 117:     $clientContext.AuthenticationMode =
 118:         [Microsoft.SharePoint.Client.ClientAuthenticationMode]::Default
 119:     $securePassword = ConvertTo-SecureString $password -AsPlainText -Force
 120:     $credentials =
 121:         New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username,
 122:             $securePassword)
 123:     $clientContext.Credentials = $credentials
 124:     $web = $clientContext.Web
 125:     $site = $clientContext.Site
 126:     $clientContext.Load($web)
 127:     $clientContext.Load($site)
 128:     $clientContext.ExecuteQuery()
 129:  
 130:     Upload-Script $clientContext $clientContext.Web
 131:     Add-Link $clientContext $clientContext.Web "~sitecollection/SiteAssets/foo.js" 10001
 132: }
 133:  
 134: Start-Transcript -Path "output.log" -Append -Force -Confirm:$false
 135:  
 136: # initialize client context
 137: $clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($adminWebAppUrl)    
 138: $clientContext.RequestTimeOut = 1000 * 60 * 10;
 139: $clientContext.AuthenticationMode =
 140:     [Microsoft.SharePoint.Client.ClientAuthenticationMode]::Default
 141: $securePassword = ConvertTo-SecureString $password -AsPlainText -Force
 142: $credentials =
 143:     New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username,
 144:         $securePassword)
 145: $clientContext.Credentials = $credentials
 146: $web = $clientContext.Web
 147: $site = $clientContext.Site
 148: $clientContext.Load($web)
 149: $clientContext.Load($site)
 150: $clientContext.ExecuteQuery()
 151:  
 152: $tenant = New-Object "Microsoft.Online.SharePoint.TenantAdministration.Tenant" -ArgumentList $clientContext
 153: $props = $tenant.GetSiteProperties(0, $true)
 154: $clientContext.Load($props)
 155: $clientContext.ExecuteQuery()
 156:  
 157: foreach($sp in $props)
 158: {
 159:     Add-Custom-Action $sp.Url
 160: }
 161:  
 162: Stop-Transcript

In this script we at first enumerate all tenant site collections (lines 136-160). For each site collection we at first upload custom javascript file to Site Assets doclib (lines 40-78) and then add custom action with script reference on added javascript file (lines 80-110). As result our custom javascript file will be added to all pages in all site collections in Sharepoint Online web app.

Add custom javascript file to all pages in on-premise Sharepoint site collection without modifying masterpage

If you need to inject custom javascript file to all pages in all sub sites in Sharepoint site collection you may edit site master page and system master page and add appropriate <script> tag there. However if you have many site collections or if your site collections don’t use publishing feature it may not be very convenient. There is another way to add custom javascript to all pages: site collection’s custom actions (SPSite.UserCustomActions) with ScriptLink location. Here is PowerShell script which can be used in order to add custom action to each site collection in specified web application with custom javascript reference:

   1: param( 
   2:     [string]$url
   3: )
   4:  
   5: function Add-Custom-Action($site, $actionUrl, $sequence)
   6: {
   7:     Write-Host "Add custom action $actionUrl on" $site.Url -foregroundcolor green
   8:     $actions = $site.UserCustomActions
   9:     $contains = $false
  10:     foreach ($a in $actions)
  11:     {
  12:         if ($a.ScriptSrc.ToLower() -eq $actionUrl.ToLower())
  13:         {
  14:             $contains = $true
  15:             break
  16:         }
  17:     }
  18:  
  19:     if ($contains)
  20:     {
  21:         Write-Host "    Custom action is already added" -foregroundcolor yellow
  22:         return
  23:     }
  24:  
  25:     $action = $site.UserCustomActions.Add()
  26:     $action.Location = "ScriptLink"
  27:     $action.ScriptSrc = $actionUrl
  28:     $action.Sequence = $sequence
  29:     $action.Update()
  30: }
  31:  
  32: Start-Transcript -Path "output.log" -Append -Force -Confirm:$false
  33:  
  34: if (-not $url)
  35: {
  36:     Write-Host "Specify web app url in url parameter" -foregroundcolor red
  37:     return
  38: }
  39:  
  40: $webApp = Get-SPWebApplication $url
  41: $webApp.Sites | ForEach-Object { Add-Custom-Action $_ "/_layouts/15/test/foo.js" 10001 }
  42:  
  43: Stop-Transcript

This script goes through all site collections and adds custom action to each of them. Before to add custom action it checks whether action with specified url is already added and if yes, skips current site collection, so it is safe to call this script many times for the same web app (always create your script with assumption that they may be executed many times for the same site). As result you will have your custom javascript available in all pages in all sub sites in all site collections, including list forms and application layouts pages.

Please note that this script uses basic Sharepoint object model and can be used with on-premise Sharepoint site only. In future posts I will show how to do the same using client object model which can be used in Sharepoint Online.

Update 2016-01-24: see Add custom javascript file to all pages in Sharepoint Online site collection for example of similar script for Sharepoint Online.