Load Script with web relative URL

It is good practice to have often used functions in a separate file that could be loaded by any html- or JavaScript-file that needs the functions. I often create htm-files with html and Javascript and use the Content Editor Webpart on a webpart page or wiki page to use these small customizations. In SharePoint Online I was struggling with loading script files with a relative path like “../scripts/functionLib.js”, what should be fine, when my page is stored in the Site Pages library, like in this snippet:


<script src="../Scripts/functionLib.js"></script>

What happens? When having the file in a library in SharePoint Online, anything removes the two dots (“..”). The result: the script could not be found, the page does not behave as expected any more. The dots are not removed directly after I store the file in the library, it happens one or two days later.

So, I have to load the script file in any other way, but I still want to be able to use site or web relative paths. To solve this problem, the functions SP.SOD.registerSod and SP.SOD.executeFunc will do the trick. Here is a simple example for a script, where I additionally use the function SP.SOD.registerSodDep:


<div>
MyParameter:&nbsp;<span id="myParameter"></span>
</div>

<script type="text/javascript">

SP.SOD.registerSod('functionLib.js', _spPageContextInfo.webServerRelativeUrl + '/scripts/functionLib.js');
SP.SOD.registerSod('jquery.js', 'https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js');

SP.SOD.registerSodDep('functionLib.js', 'jquery.js');

SP.SOD.executeFunc('functionLib.js', null, function() {
	var myParam = getUrlParameter("myParam");

	$('#myParameter').html(myParam);
});

</script>

This example uses the webServerRelativeUrl member of _spPageContextInfo, where SharePoint stores the relative URL of the current web site. When the script is stored in the root site of the site collection, the member siteServerRelativeUrl could be used instead.

Disable Site Creation in OneDrive (aka. MySites)

From one of our customers I was asked, if it is possible to disable the creation of subsites in OneDrive. Hm. We should not change any permissions of the user in his OneDrive site. So, by his permissions, he will always be able to create new sites. But we can remove all the web templates from the site collection. He still is able to open the “Create new site” dialog, but he is not able to choose a template for his site.

All could be done by some PowerShell. I have split the scripts into three parts. The first one is DisableSiteCreation.ps1. This will do the changes for a given url.

param (
    [Parameter(Mandatory=$true)][string] $Url
)

Write-Host "$Url.Url..."

$web = Get-SPWeb $Url

if ($web.Provisioned -eq $false)
{
    Write-Host -ForegroundColor Yellow "The web at $Url is not provisioned. Skip the web"
}
else
{
    $col = New-Object System.Collections.ObjectModel.Collection[Microsoft.SharePoint.SPWebTemplate]

    $cultures = $web.SupportedUICultures
    foreach ($culture in $cultures)
    {
        $lcid = $culture.LCID
        # Write-Host $culture.LCID

        $w = Get-SPWeb $Url
        $w.SetAvailableWebTemplates($col, $lcid)
        $w.Update()

        Write-Host "Removed web templates in $Url for LCID $lcid"
    }
}

The second script is RunOnSiteCollections.ps1 that is used to call the first script. The path to the first script (“DisableSiteCreation.ps1”) is passed as a parameter to this script.

param (
    [Parameter(Mandatory=$true)][string] $WebApp,
    [Parameter(Mandatory=$true)][string] $Script,
    [string] $FilterTemplate,
    [string] $FilterConfiguration
)

$wa = Get-SPWebApplication $WebApp -ErrorAction SilentlyContinue

if ($wa -eq $null)
{
    Write-Host -ForegroundColor Red "There is no web application in url $WebApp"
}
else
{
    foreach ($site in $wa.Sites)
    {
        $web = $site.RootWeb

        $runOnWeb = $true

        if (($FilterTemplate -ne "") -and ($FilterConfiguration -ne ""))
        {
            if (($FilterTemplate.ToUpper() -ne $web.WebTemplate.ToUpper()) -or ($FilterConfiguration -ne $web.Configuration))
            {
                $runOnWeb = $false
            }
        }
        
        if ($runOnWeb -eq $true)
        {
            & $Script $web.Url
        }
    }
}

The last one DisableSiteCreationInMySites.ps1 is just a wrapper to call the functionality. This last script will get the url of the MySite host and will remove all the web templates from the site collections in this web application. Run this one to disable the site creation in OneDrive sites.

param (
    [Parameter(Mandatory=$true)][string] $WebApp
)

. .\RunOnSiteCollections.ps1 -WebApp $WebApp -Script .\DisableSiteCreation.ps1 -FilterTemplate SPSPERS -FilterConfiguration 10

To revert these changes, the following scripts could be used, whereby the script EnableSiteCreationInMySites.ps1 should be called.

The counterpart to the DisableSiteCreation.ps1 is EnableSiteCreation.ps1:

param (
    [Parameter(Mandatory=$true)][string] $Url
)

Write-Host "$Url.Url..."

$web = Get-SPWeb $Url

if ($web.Provisioned -eq $false)
{
    Write-Host -ForegroundColor Yellow "The web at $Url is not provisioned. Skip the web"
}
else
{
    $web.AllowAllWebTemplates()
    $web.Update()

    Write-Host "All web templates are allowed in $Url"
}

The counterpart to DisableSiteCreationInMySites.ps1 is EnableSiteCreationInMySiteHost.ps1:

param (
    [Parameter(Mandatory=$true)][string] $WebApp
)

. .\RunOnSiteCollections.ps1 -WebApp $WebApp -Script .\EnableSiteCreation.ps1 -FilterTemplate SPSPERS -FilterConfiguration 10

 

Create a TFS Backlog Item with PowerShell

Inspired by this blog post by the AIT GmbH, the following PowerShell script is created to simply create a new product backlog item in a team project in Team Foundation Server.

param (
	[string] $Url,          # the uri to the team project collection
	[string] $ProjectName,  # the name of the team project
	[string] $PBITitle,     # the title for the new product backlog item
	[string] $Area          # the area path, could be empty
)

#Load Reference Assemblies 
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Client") 
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.WorkItemTracking.Client") 
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.ProjectManagement") 

$TeamProjectCollection = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($Url) 
$TeamProjectCollection.EnsureAuthenticated()

# Service instances 

$css4 = 
   ($TeamProjectCollection.GetService([type]"Microsoft.TeamFoundation.Server.ICommonStructureService4")) -as 
     [Microsoft.TeamFoundation.Server.ICommonStructureService4] 

$teamService = 
   ($TeamProjectCollection.GetService([type]"Microsoft.TeamFoundation.Client.TfsTeamService")) -as 
     [Microsoft.TeamFoundation.Client.TfsTeamService] 

$teamConfigService = 
   ($TeamProjectCollection.GetService([type]"Microsoft.TeamFoundation.ProcessConfiguration.Client.TeamSettingsConfigurationService")) -as 
     [Microsoft.TeamFoundation.ProcessConfiguration.Client.TeamSettingsConfigurationService] 

$store = 
   ($TeamProjectCollection.GetService([type]"Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore")) -as 
     [Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore]

$project = $store.Projects[$ProjectName]

if ($project -eq $null)
{
	throw "The team project $ProjectName could not be found"
}


# Get the work item type

$WorkItemType = "Product Backlog Item"  # this will only work with projects with the Scrum template

$wit = $project.WorkItemTypes[$WorkItemType] 

if (!$wit) { 
        throw "The WorkItemType $WorkItemType could not be found in project $ProjectName" 
} 


# Create Product Backlog Item (PBI) 

Write-Host "Creating Product Backlog Item" 

$pbi = New-Object Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem ($wit) 

$pbi.Title = $PBITitle 

# $pbi.Description = "Hello World."

if ($Area -ne "")
{
	$pbi.AreaPath = $Area
}

$pbi.Save()

Write-Host -ForegroundColor Green "Done."

Show Web Properties with JavaScript

Especially in SharePoint Online it could be difficult to make the content of the property bag for a site visible. With JavaScript in SharePoint 2013 or SharePoint Online this task could be done very easy. Just save the following script to a file and upload this file to the SiteAssets library in the SharePoint site. Create a new page in the SharePoint site and put a Content Editor webpart onto this page. Set the script url in the properties of the Content Editor webpart to the url to the uploaded script file. After saving and reloading the page click the button to display the web properties.

<style>
.webPropertyRow {
	padding-left: 4px;
	padding-right: 4px;
}
</style>

<script type="text/javascript" language="javascript">

	var clientContext = null;
	var web = null;
	var properties = null;

	function runCode() {
        this.clientContext = new SP.ClientContext.get_current();

        if (this.clientContext != undefined && this.clientContext != null) {
            this.properties = clientContext.get_web().get_allProperties();
			
			this.clientContext.load(this.properties);
			
			this.clientContext.executeQueryAsync(
				Function.createDelegate(this, this.gotWebProperties), 
				Function.createDelegate(this, this.onQueryFailed));
		}
	}
	
	function gotWebProperties() {
		document.getElementById("webProperties").innerHTML = "&nbsp;";
		
		var table = document.createElement("table");
		table.setAttribute("border", "1");
		
		var allProperties = this.properties.get_fieldValues();
		
		for (property in allProperties) {
			var row = document.createElement("tr");
			
			var cell1 = document.createElement("td");
			cell1.setAttribute("class", "webPropertyRow");
			
			cell1.innerHTML = property;
			
			var cell2 = document.createElement("td");
			cell2.setAttribute("class", "webPropertyRow");
			
			cell2.innerHTML = allProperties[property];
			
			row.appendChild(cell1);
			row.appendChild(cell2);
			
			table.appendChild(row);
		}
		
		document.getElementById("webProperties").appendChild(table);
	}
	
    function onQueryFailed(sender, args) {
        alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
    }

</script>

<input id="Button1" type="button" value="Show Web Properties" onclick="runCode()" />
	
<br />
<div style="margin-top:20px;">&nbsp;</div>

<div id="webProperties"></div>

SharePoint – Add Navigation with JavaScript

With a short JavaScript it is possible to add navigation nodes in a SharePoint site (quicklaunch and top navigation bar). This is useful, when an internal link should be added. Adding internal links is not possible using the user interface in the browser in SharePoint. In the following example we define the new nodes in an array at the beginning. The code block takes this array and sets the navigation. It makes sense to remove the existing entries at the beginning. An example for removing could be found here.

<script type="text/javascript" language="javascript">

    var navArray = [
	{ title:"Home", url:"/sites/gms-portal", isExternal:false },
	{ title:"Microsoft", url:"http://www.microsoft.com", isExternal:true },
	{ title:"Google", url:"http://www.google.com", isExternal:true },
	{ title:"Bing", url:"http://www.bing.com", isExternal:true }
    ];

    function checkArray() {
		for (var i = 0; i < navArray.length; i++) {
			var navObject = navArray[i];
			
			alert(navObject.title);
		}
	}
	
    var navigationNodeCollection = null;
    var nnci = null;
	
	function addNavigationNodes() {
        var clientContext = new SP.ClientContext.get_current();

        if (clientContext != undefined && clientContext != null) {
            var web = clientContext.get_web();

            // Get the Quick Launch navigation node collection.
            // this.quickLaunchNodeCollection = web.get_navigation().get_quickLaunch();
            
            // Get the Top Navigation navigation node collection.
			this.navigationNodeCollection = web.get_navigation().get_topNavigationBar();

			for (var i = 0; i < navArray.length; i++) {
				var navObject = navArray[i];
				var navTitle = navObject.title;
				var navUrl = navObject.url;
				var navIsExternal = navObject.isExternal;

				// Set properties for a new navigation node.
				this.nnci = new SP.NavigationNodeCreationInformation();
				nnci.set_title(navTitle);
				nnci.set_url(navUrl);
				nnci.set_isExternal(navIsExternal);
            
				// Create node as the last node in the collection.
				nnci.set_asLastNode(true);
				this.navigationNodeCollection.add(nnci);
			}

			clientContext.load(this.navigationNodeCollection);
			clientContext.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));
		}
	}
	
    function runCode() {
		addNavigationNodes();
    }

    function onQuerySucceeded() {
        alert("Nodes are added to the navigation.");
    }

    function onQueryFailed(sender, args) {
        alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
    }

</script>

<input id="Button1" type="button" value="Set Navigation" onclick="runCode()" />
	
<br />
<div style="marginTop:20px;">&nbsp;</div>

<input id="Button2" type="button" value="Check Navigation Array" onclick="checkArray()" />

Take the script and save it as a file. Copy this file to the SiteAssets library in your SharePoint site. Create a new page and add a Content Editor webpart on the page. In the properties of the webpart enter the complete url to the script file you saved in the SiteAssets library.

SharePoint – Remove Navigation Nodes with JavaScript

Sometimes it is necessary to remove all the nodes from the navigation (quicklaunch or top navigation). One reason could be that the navigation should be set different from the original after the site was created. To remove all the nodes by JavaScript the following script could be used.

<script type="text/javascript" language="javascript">

	var clientContext = null;
    var navigationNodeCollection = null;
    var nnci = null;
	
	function removeNavigationNodes() {
        this.clientContext = new SP.ClientContext.get_current();

        if (clientContext != undefined && clientContext != null) {
            var web = clientContext.get_web();

            // Get the Quick Launch navigation node collection.
            // this.quickLaunchNodeCollection = web.get_navigation().get_quickLaunch();
            
            // Get the Top Navigation navigation node collection.
			this.navigationNodeCollection = web.get_navigation().get_topNavigationBar();

			this.clientContext.load(this.navigationNodeCollection);
			this.clientContext.executeQueryAsync(
				Function.createDelegate(this, this.onQuerySucceeded), 
				Function.createDelegate(this, this.onQueryFailed));
        }
	}
	
    function onQuerySucceeded() {
		// remove all nodes from the navigation node
		for(var i = this.navigationNodeCollection.get_count() - 1; i >= 0; i--) {
            this.navigationNodeCollection.get_item(i).deleteObject();
		}

		this.clientContext.executeQueryAsync(
			Function.createDelegate(this, this.onQuerySucceededPartTwo), 
			Function.createDelegate(this, this.onQueryFailed));

	}

    function onQuerySucceededPartTwo() {
        alert("Nodes removed");
    }

    function onQueryFailed(sender, args) {
        alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
    }

    function runCode() {
		removeNavigationNodes();
    }

</script>

<input id="Button1" type="button" value="Set Navigation" onclick="runCode()" />

Take the script and save it as a file. Copy this file to the SiteAssets library in your SharePoint site. Create a new page and add a Content Editor webpart on the page. In the properties of the webpart enter the complete url to the script file you saved in the SiteAssets library.

Distribute DSC Resource Kit with PowerShell DSC

PowerShell DSC is a perfect option to define the state of a machine in a declarative way. Therefore PowerShell comes with a small set of resources that could be used in the definition. For special cases (create web sites, create or configure file shares, etc.) some additional resources a provided in the PowerShell DSC Resource Kit (https://gallery.technet.microsoft.com/scriptcenter/DSC-Resource-Kit-All-c449312d). When you want to use a resource from the resource kit, this resource must be made available on the target node. The next steps will show how to distribute the modules from the resource kit using PowerShell DSC, when using the push mode. The setup in this example is very simple. There is one server acting as a DSC controller and two nodes. From the DSC controller we will push the configuration to the nodes.

First of all we need to download the PowerShell DSC Resource Kit from the Technet Gallery. The downloaded zip-file will be extracted to a folder on the DSC controller.


In our case the files are extracted to the folder “C:\DSC Resources”. The modules we need to distribute will be available in “C:\DSC Resources\All Resources”. All these folders contain the module definitions that need to be copied to the Modules folder in “C:\Program Files\WindowsPowerShell\Modules”, so they could be imported and used.

For this step we use the following script that is saved as “PrepareDSCResourceKitFolder.ps1”:

# prepare DSCResourceKit folder

$sourceDir = 'C:\DSC Resources\All Resources'
$targetDir = 'C:\DSCResourceKit'

$folders = Get-ChildItem -Path $sourceDir | Where-Object {$_.PSIsContainer}

foreach ($folder in $folders)
{
	$f = Get-Item $folder.FullName
    
    Copy-Item $f $targetDir -Recurse
}

Get-ChildItem -Recurse -Path $targetDir | Unblock-File

Important is the last line in the script, because the psm1-files are blocked, when we extract the zip-file with the resource kit.

At this point we have the resource kit files in a structure that could be copied to our nodes. So the final step is simple, because we just need to create a configuration in PowerShell DSC that will copy these files and folders to our nodes.

$DscResourceKit = "\\Dsc-Controller\DscResourceKit"

$configData = 
@{
    AllNodes = 
    @(
        @{
            NodeName = "DSC-Controller"
            Role = "Controller"
        }
        @{
            NodeName = "Node1"
            Role = "WebServer"
        }
        @{
            NodeName = "Node2"
            Role = "WebServer"
        }
    );

    NonNodeData = ""   
}



Configuration EnsureDscResources
{
	Node $AllNodes.Where{$_.Role -ne ""}.NodeName
	{
		File DscResourceKit
		{
			Ensure = 'Present'
            SourcePath = $DscResourceKit
            DestinationPath = "$env:ProgramFiles\WindowsPowerShell\Modules"
            Type = "Directory"
            Recurse = $true
            Force = $true
		}

        Log DscResourceKitFinished
        {
			Message = 'CopyFilesTester finished.'
            DependsOn = '[File]DscResourceKit'
        }
	}
}

EnsureDscResources -ConfigurationData $configData

Start-DscConfiguration -Path .\EnsureDscResources -Wait

That’s it. Just to test, whether we can use the modules or not, we just make a simple configuration that will create a folder on our nodes an share this folder. In this sample the configuration (list of nodes and roles) is separated from the desired state definition.

@{
    AllNodes = 
    @(
        @{
            NodeName = "Node1"
            Role = "WebServer"
        }
        @{
            NodeName = "Node2"
            Role = "WebServer"
        }
    );

    NonNodeData = ""   
}

 


Configuration CreateSampleShare
{
    Import-DscResource -ModuleName cFileShare

    node $AllNodes.Where{$_.Role -ne ""}.NodeName
    {
        File DropFolder
        {
            Ensure = 'Present'
            Type = 'Directory'
            DestinationPath = 'C:\DropFolder'
        }

        cCreateFileShare CreateShare
        {
            ShareName = 'DropFolder'
            Path      = 'C:\DropFolder'
            Ensure    = 'Present'
            DependsOn = '[File]DropFolder'
        }

        cSetSharePermissions SetPermissions
        {
            ShareName         = 'DropFolder'
            DependsOn         = '[cCreateFileShare]CreateShare'
            Ensure	          = 'Present'
            #FullAccessUsers   = @('[user1]')
            #ChangeAccessUsers = @('[user2]')
            ReadAccessUsers   = @('Everyone')
        }
    }
}

CreateSampleShare -ConfigurationData "C:\Scripts\ConfigData.psd1"

Start-DscConfiguration -Path .\CreateSampleShare -Wait

From now on we can use the modules from the PowerShell DSC Resource Kit in all our configuration definitions. When we create new (own) resources, we can distribute them with the same technique to the nodes in our environment.

Follow

Get every new post delivered to your Inbox.