CHUVASH.eu

CHunky Universe of Vigourous Astonishing SHarepoint :)

Tag Archives: PowerShell

PowerShell: Copy an entire document library from SharePoint 2007 to disk

For a while ago I needed to copy all files from a document library within a SharePoint 2007 site to the hard drive. So I didn’t need to copy files from SharePoint to SharePoint so I couldn’t use the stsadm -o export command or Chris O’Brien’s nice SharePoint Content Deployment Wizard. I came across the SPIEFolder application which should work with SharePoint 2007 and 2010. It has a site on codeplex: spiefolder.codeplex.com, but neither the binary nor the source code can be downloaded from there. After some searching I found the binary in the author’s skydrive. The fact that the source code was not available seemed as an disanvantage because I could not know what code was run. Nevertheless I tried it out and it didn’t work:

spiefolder -o export -url "http://dev/Documents" -directory c:\tolle\Documents –recursive

I got the following error:

The Web application at http://dev/Documents could not be found. Verify that you have typed the URL correctly. If the URL should be serving existing content, the system administrator may need to add a new request URL mapping to the intended application.

So I wrote my own code to copy the documents. To write a console application feels so yesterdayish, so it is written in PowerShell. Even if there are no PowerShell snapins for SharePoint 2007, you have access to the entire Server Object Model, the only thing you have to do is to load the SharePoint assembly:

[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")

Then you can instantiate all SharePoint objects like in C#, but in a PowerShell way:

$site = new-Object Microsoft.SharePoint.SPSite("http://dev")
$web = $site.OpenWeb()

You can even download a module for emulating cmdlets: Get-SPWeb, Get-SPWebApplication and Get-SPFarm, written by Natalia Tsymbalenko (sharing-the-experience.blogspot.com) to get started or just to find some inspiration.

I have created a ps1-script which only does one thing – it copies an entire document library to disk. Much of inspiration to structure the script comes from “Delete-SPListItems” (sharepointryan.com).

Here it is: Pull-Documents.ps1

<#
.Synopsis
    Use Pull-Documents to copy the entire document library to disk
.Description
    This script iterates recursively over all directories and files in a document library and writes binary data to the disk
    The structure is kept as in the Document library
    It is mainly written for SharePoint 2007, but it works even in SharePoint 2010
.Example
    Pull-Document -Url http://dev -Library "Shared Documents"
.Notes
    Name: Pull-Documents.ps1
    Author: Anatoly Mironov
    Last Edit: 2012-12-03
    Keywords: SPList, Documents, Files, SPDocumentLibrary
.Links
    https://sharepointkunskap.wordpress.com
    http://www.bool.se
.Inputs
    None
.Outputs
    None
#Requires -Version 1.0
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)][System.String]$Url = $(Read-Host -prompt "Web Url"),
[Parameter(Mandatory=$true)][System.String]$Library = $(Read-Host -prompt "Document Library")
)
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")

$site = new-object microsoft.sharepoint.spsite($Url)
$web = $site.OpenWeb()
$site.Dispose()

$folder = $web.GetFolder($Library)
$folder # must output it otherwise "doesn't exist" in 2007

if(!$folder.Exists){
    Write-Error "The document library cannot be found"
    $web.Dispose()
    return
}

$directory = $pwd.Path

$rootDirectory = Join-Path $pwd $folder.Name

if (Test-Path $rootDirectory) {
    Write-Error "The folder $Library in the current directory already exists, please remove it"
    $web.Dispose()
    return
}

#progress variables
$global:counter = 0
$global:total = 0
#recursively count all files to pull
function count($folder) {
    if ($folder.Name -ne "Forms") {
        $global:total += $folder.Files.Count
        $folder.SubFolders | Foreach { count $_ }
    }
}
write "counting files, please wait..."
count $folder
write "files count $global:total"

function progress($path) {
    $global:counter++
    $percent = $global:counter / $global:total * 100
    write-progress -activity "Pulling documents from $Library" -status $path -PercentComplete $percent
}

#Write file to disk
function Save ($file, $directory) {
    $data = $file.OpenBinary()
    $path = Join-Path $directory $file.Name
    progress $path
    [System.IO.File]::WriteAllBytes($path, $data)
}

#Forms folder doesn't need to be copied
$formsDirectory = Join-Path $rootDirectory "Forms"

function Pull($folder, [string]$directory) {
    $directory = Join-Path $directory $folder.Name
    if ($directory -eq $formsDirectory) {
        return
    }
    mkdir $directory | out-null

    $folder.Files | Foreach { Save $_ $directory }

    $folder.Subfolders | Foreach { Pull $_ $directory }
}

Write "Copying files recursively"
Pull $folder $directory

$web.Dispose()

I have tested this script in SharePoint 2007 and 2010. It works. Let me know if you find this useful or have some suggestions.

Advertisements

Run web.config-dependant code in PowerShell

PowerShell is a great tool. It helps in SharePoint administration and tasks automation. Today I needed to provision a webpart on many similar pages. This third-party webpart’s constructor instantiates a dataaccess service and uses a connectionstring which is stored in the web.config file. So the webpart creation failed until I found a way to load the configuration into powershell.

First you can create a simple file powershell.exe.config, put it into $pshome (C:\Windows\System32\WindowsPowerShell\v1.0). In that file you can specify the connectionstring in the same way like in the web.config (or app.config). But it is not secure: the connectionstring can be changed in the future, and you really don’t want to copy your connectionstrings around. So there is a better way: Check out Ohad Israeli’s blog post about how a configuration file can be bound dynamically in a .net assembly: Binding to a custom App.Config file.

In PowerShell you can do the same, all we need is to modify the syntax: StackOverflow: Powershell Calling .NET Assembly that uses App.config:

[System.AppDomain]::CurrentDomain.SetData("APP_CONFIG_FILE", $config_path)

In SharePoint the web.config file resides in:

C:\inetpub\wwwroot\wss\VirtualDirectories\80\web.config

So include this in the script which invokes web.config dependant code:

$config_path = "C:\inetpub\wwwroot\wss\VirtualDirectories\80\web.config"
[System.AppDomain]::CurrentDomain.SetData("APP_CONFIG_FILE", $config_path)

The ability to bind the configuration file dynamically can be even helpful in another part of the SharePoint world – timerjobs. The timer jobs are executed by owstimer.exe. This service has an own configuration file: {hive}\BIN\OWSTIMER.EXE.CONFIG If the timer job configuration shares connectionstrings or appSettings with web.config, it should be theoretically possible to bind the web.config to owstimer. Leave a comment below if you find this information useful.

Create SPGroup in PowerShell

Thanks to Ryan for sharing powershell functions. I used New-SPGroup which I altered. Now You can define which permissions will be given to the new group. You can even create groups without default users. Here it comes:

 
function New-SPGroup {
<#
.Synopsis
	Use New-SPGroup to create a SharePoint Group.
.Description
	This function uses the Add() method of a SharePoint RoleAssignments property in an SPWeb to create a SharePoint Group.
.Example
New-SPGroup -Web http://intranet -GroupName "Test Group" -OwnerName DOMAIN\User -MemberName DOMAIN\User2 -Description "My Group" -Role "Read"
	This example creates a group called "Test Group" in the http://intranet site, with a description of "My Group".  The owner is DOMAIN\User and the first member of the group is DOMAIN\User2 and adds "Limited Access".
	C:\PS>New-SPGroup -Web http://intranet -GroupName "Test Group" -OwnerName DOMAIN\User -MemberName DOMAIN\User2 -Description "My Group" -Role "Read"
	This example creates a group called "Test Group" in the http://intranet site, with a description of "My Group".  The owner is DOMAIN\User and the first member of the group is DOMAIN\User2 and adds "Read" access.
	Pay attention to the role definition names. They must be provided in the language of your site.
.Notes
	Name: New-SPGroup
	Author: Ryan Dennis, Anatoly Mironov
	Last Edit: 2012-11-05
	Keywords: New-SPGroup, spgroup, permissions
.Link
	http://www.sharepointryan.com
 	http://twitter.com/SharePointRyan
	https://sharepointkunskap.wordpress.com
.Inputs
	None
.Outputs
	None
#Requires -Version 2.0
#>
	[CmdletBinding()]
	Param(
	[Microsoft.SharePoint.PowerShell.SPWebPipeBind]$Web,
	[string]$GroupName,
	[string]$OwnerName,
	[string]$MemberName,
	[string]$Role,
	[string]$Description
	)
	$SPWeb = $Web.Read()
	if ($SPWeb.SiteGroups[$GroupName] -ne $null){
		throw "Group $GroupName already exists!"	
	}

	if ($Role) {
		$roleDefinition = $SPWeb.RoleDefinitions[$Role]
		if (!$roleDefinition) {
			throw "Role Definition $Role doesn't exist!"
		}
	}

	if ($SPWeb.Site.WebApplication.UseClaimsAuthentication){
		$op = New-SPClaimsPrincipal $OwnerName -IdentityType WindowsSamAccountName
		$owner = $SPWeb | Get-SPUser $op
		if ($MemberName) {
			$mp = New-SPClaimsPrincipal $MemberName -IdentityType WindowsSamAccountName
			$member = $SPWeb | Get-SPUser $mp
		}
	}
	else {
	$owner = $SPWeb | Get-SPUser $OwnerName
		if ($MemberName) {
			$member = $SPWeb | Get-SPUser $MemberName
		}
	}

	$SPWeb.SiteGroups.Add($GroupName, $owner, $member, $Description)
	$SPGroup = $SPWeb.SiteGroups[$GroupName]	
	$roleAssignment = new-object Microsoft.SharePoint.SPRoleAssignment($SPGroup)
	if ($Role) {
		$roleAssignment.RoleDefinitionBindings.Add($roleDefinition)
	}
	$SPWeb.RoleAssignments.Add($roleAssignment)
	$SPWeb.Dispose()
	return $SPGroup
}

SharePointRyan

Disclaimer: This post and function is 100% taken from the book by Gary Lapointe and Shannon Bray entitled “Automating Microsoft SharePoint 2010 Administration with Windows PowerShell 2.0.”

This post goes hand in hand with my recent post called “Retrieve SharePoint Groups using PowerShell.”  In that post, we used a simple PowerShell Function to retrieve an SP Group and return the object in our command window.  In this function, we’re going to take the work a step further – we will use our function to create a new SharePoint Group.

Again, I have taken the code from Gary and Shannon’s book – the only addition I have made is comment-based help.  Since I intend to use these functions, I like to have help so I can go back and see examples and things like that.

The function provided in the book is very nice, it consists of…

View original post 39 more words

Creating custom powershell cmdlet

Why

I need to to activate a feature in PowerShell and specify some properties. Simple? Yes. Possible? No. In the default Enable-SPFeature cmdlet you can’t specify any properties:

Enable-SPFeature –Identity "b5eef7d1-f46f-44d1-b53e-410f62032846" -URL http://dev

We can of course easily add properties when activating features in onet.xml:

<!-- Publishing Resources -->
<Feature ID="AEBC918D-B20F-4a11-A1DB-9ED84D79C87E">
  <Properties xmlns="http://schemas.microsoft.com/sharepoint/">
    <Property Key="AllowRss" Value="false" />
    <Property Key="SimplePublishing" Value="false" />
  </Properties>
</Feature>

So I went to SharePoint StackExchange and asked the question. Then I realized: the standard Sharepoin API doesn’t support this neither. The methods of SPFeatureCollection which have SPFeatureProperties as parameter are internal. So the only way is to use Reflection like Hristo Pavlov (2008) and Yaroslav Pentsarsky (2010) suggest. So why not to try to create a cmdlet?

What

I want to create a custom cmdlet: Enable-SPFeatureWithProperties. It should look like this:

$properties = @{"Test" = "SPLENDID" }
Enable-SPFeatureWithProperties `
      –Identity "b5eef7d1-f46f-44d1-b53e-410f62032846" `
      -URL http://dev `
      -Properties $properties

The cmdlet code will be written in C# and published on github. It is a part of my project called sp-lend-id and like all other parts in sp-lend-id it will have a Chuvash word as its name: Taprat (/taprat/ meaning “Enable” 🙂 )

How

First I create a simple cmdlet just to get started. Then I test the reflection code to activate a feature with properties. Then, if this works, I will bring the pieces together and create my cmdlet.

Simple cmdlet

The best cmdlet tutorial (text, images, code and videocast) is created by Saveen Reddy. I just followed his sample. I created a new class library project and a class for the demo cmdlet: Get_DemoNames.cs.

By the way, if you don’t have th powershell dll as Saveen Reddy describes, add manually the reference to the csproj-file:

<Reference Include="System.Management.Automation" />

Build and test the cmdlet:

Activating the feature

Create a very simple Feature and Feature Receiver which takes properties and does something with them, like Yaroslav suggested:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
	var web = properties.Feature.Parent as SPWeb;
	if (web != null)
	{
		var allow = web.AllowUnsafeUpdates;
		web.AllowUnsafeUpdates = true;
		if (properties.Feature.Properties["Test"] != null)
		{
			web.Title = properties.Feature.Properties["Test"].Value;
			web.Update();
		}
		web.AllowUnsafeUpdates = allow;
	}
}

After deploying this feature we can create a simple console application to test the reflection code. First, the extension for SPFeature to be able to invoke the internal methods:

public static class SPFeatureExtensions
{
    public static SPFeature ActivateFeature(this SPFeatureCollection features, Guid featureId, Dictionary&lt;string, string&gt; activationProps)
    {
        var propCollConstr = typeof(SPFeaturePropertyCollection).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
        var properties = (SPFeaturePropertyCollection) propCollConstr.Invoke(new object[] { null });
        foreach (var key in activationProps.Keys)
        {
            properties.Add(new SPFeatureProperty(key, activationProps[key]));
        }           
        return ActivateFeature(features, featureId, properties);
    }
    private static SPFeature ActivateFeature(this SPFeatureCollection features, Guid featureId, SPFeaturePropertyCollection properties)
    {          
        if (features[featureId] != null)
        {
            // The feature is already activated. No action required
            return null;
        }
        var type = typeof(SPFeatureCollection);

        // now we have to get "AddInternal" Method with this signature:
        //internal SPFeature AddInternal(Guid featureId, Version version, SPFeaturePropertyCollection properties, bool force, bool fMarkOnly, SPFeatureDefinitionScope featdefScope)

        var param = new[]
                        {
                            typeof (Guid), typeof (Version), typeof (SPFeaturePropertyCollection), typeof (bool),
                            typeof (bool), typeof (SPFeatureDefinitionScope)
                        };
        var addInternal = type.GetMethod("AddInternal", BindingFlags.Instance | BindingFlags.NonPublic, null, param, null);
        if (addInternal == null)
        {
            // failed to find the method
            return null;
        }
        var result = addInternal.Invoke(features, new object[] { featureId, null, properties, false, false, SPFeatureDefinitionScope.Farm });
        return result as SPFeature;
    }
}

There are some changes compared to Yaroslav’s code:

  • The testing if feature is enabled happens without internal Get Method, just by (features[featureId] != null)
  • The internal “Add” method now is called “AddInternal”
  • The parameters which have to be passed to the internal add method are changed, too.

I found the signature of the “AddInternal” using Reflector.NET 6:

internal SPFeature AddInternal(Guid featureId, 
				Version version, 
				SPFeaturePropertyCollection properties, 
				bool force, 
				bool fMarkOnly, 
				SPFeatureDefinitionScope featdefScope)
{
    return this.AddInternalWithName(featureId, null, 
            version, properties, force, fMarkOnly, featdefScope);
}
Testing the code in a console application

Just a few lines of code to test and debug the code:

static void Main(string[] args)
{
	using (var site = new SPSite("http://dev"))
	{
		using (var web = site.OpenWeb())
		{
			var activationProps = new Dictionary
				{{"Test", "Title changed by feature in activation"}};
			web.Features.ActivateFeature(
                          new Guid("b5eef7d1-f46f-44d1-b53e-410f62032846"), 
                          activationProps);
		}
	}
}

The pre-condition is that the test feature is deployed, then it works:

Running it in Powershell

Now, when we know the code works, we can test it in powershell. We create three params:

[System.Management.Automation.Parameter(Position = 0, Mandatory = true)] 
public string Identity;

[System.Management.Automation.Parameter(Position = 1, Mandatory = true)]
public string Url;

[System.Management.Automation.Parameter(Position = 2, Mandatory = false)]
public System.Collections.Hashtable Properties;

The third parameter is Hashtable because the main “dictionary” object in PowerShell is Hashtable. So we must convert the passed hashtable into a Dictionary:

private Dictionary<string, string> GetProperties()
{
    var dictionary = new Dictionary<string, string>();
    if (Properties != null)
    {
        foreach (var key in Properties.Keys)
        {
            dictionary.Add((string)key, (string)Properties[key]);
        }
    }
    return dictionary;
}

Then the only thing we have to do is to copy the code from the Main method in console app into the ProcessRecord method:

protected override void ProcessRecord()
{
    var properties = GetProperties();
    WriteObject(properties);
    using (var site = new SPSite(Url))
    {
        using (var web = site.OpenWeb())
        {
            var features = web.Features;
            var id = new Guid(Identity);
            features.ActivateFeature(id, properties);
        }
    }
}

Now we can rebuild the solution and run the powershell by importing the module:

$p = @{"Test" = "Tjena" }
Enable-SPFeatureWithProperties -Identity "b5eef7d1-f46f-44d1-b53e-410f62032846" -Url "http://dev" -Properties $p
Creating PSSnapin

So the cmdlet is completed. There is one thing left which I want to try: creating pssnapin which one can add in an easy way to a powershell session. To create a pssnapin we have to create class in the project and extend it from PSSnapin. This class just has some properties: Name, Description and Vendor:

[RunInstaller(true)]
public class TapratInstaller : PSSnapIn
{
    public override string Name
    {
        get
        {
            return "sp-lend-id.taprat";
        }
    }

    public override string Vendor
    {
        get 
        { 
            return "Anatoly Mironov";
        }
    }

    public override string Description
    {
        get
        {
            return
                "This includes an experimental cmdlet to activate sharepoint features and specify custom properties";
        }
    }
}

To install the pssnapin, run these commands in powershell as Administrator:

Set-Alias installutil $env:windir\Microsoft.NET\Framework64\v2.0.50727\installutil.exe
installutil C:\code\sp-lend-id\taprat\cmdlet\sp-lend-id.taprat\bin\Debug\sp-lend-id.taprat.dll

Pay attention that we have to run installutil.exe from Framework64, not Framework. Otherwise the pssnapin won’t be accessible.

After the pssnapin has been installed we can see it by running:

Get-PSSnapin -registered

Now all we have to is to add the newly created pssnapin and run the commands:

Add-PSSnapin sp-lend-id.taprat
Get-DemoNames -Prefix "Hello "

And the desired:

$properties = @{"Test" = "SPLENDID" }
Enable-SPFeatureWithProperties `
      –Identity "b5eef7d1-f46f-44d1-b53e-410f62032846" `
      -URL http://dev `
      -Properties $properties

Of course, in order this to work, we have to deactivate our test feature first. Then the cmdlet activates our test feature which only updates web title in this experiment. It works. Splendid!

Code

The code can be found on sp-lend-id project’s code repository on github: https://github.com/mirontoli/sp-lend-id

DISCLAIMER:

This code is just a lab code for experimenting and learning purposes. Don’t use it in production environments unless you test it yourself. As Per Jacobsen pointed in his answer on Sharepoint StackExchange, the internal methods can vary in different releases and patches of SharePoint, which we can see here (Yaroslav’s code can’t be run anymore), and it can break your solution deployment.

Out-Gridview in PowerShell

Did you know that we can pipe the output from PowerShell into a graphical GridView? I have used PowerShell for one year and only now I discovered this nice feature. You can add filter criteria, you can filter by typing in a textbox:

Other useful output cmdlets
  • out-file
  • out-clip
  • out-null

Run powershell remotely

To run powershell remotely is really easy. First enable it on the machine which will receive the remote commands:

Enable-PSRemoting

Then go to another machine and connect to your host:

$cred = Get-Credential
Enter-PSSession -Computername dev -Credential $cred

If you want to know more about powershell remoting, I recommend “A layman’s guide…”. See even a detailed example on poshcode.

Access User Profile Properties from Powershell

To use only SPUser objects isn’t always sufficient. To get other properties we have to retrieve user profiles. Giles Hamson gives an example how to get and how to update user profile properties with powershell.

Here is an example how to get all work phones:

$url = "http://intranet/"
$site = Get-SPSite $url
$context = Get-SPServiceContext $site
$profileManager = New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager($context)
$profiles = $profileManager.GetEnumerator()

while ($profiles.MoveNext()) {
  $userProfile = $profiles.Current
  $name = $userProfile.DisplayName
  $phone = $userProfile["WorkPhone"]
  $line = '{0};{1}' -f $name, $phone
  write $line
}

If you are not sure what properties are called, see the whole list by typing:

$userProfile.Properties | select name

A wonderful christmas tree in Powershell

By the way, to capture this christmas tree I used cropper and a plugin to it called Animated Gif.

SharePointRyan

Last night I spoke at DAYSPUG in Dayton Ohio, and since Christmas is only about 10 days away – I thought it would be fun to introduce two Christmas-related PowerShell Functions that I threw together.

The two functions are:

  1. New-ChristmasTree – This function generates an awesome, and mostly tacky animated, flashing Christmas tree in the PowerShell console.
  2. Get-DaysUntilChristmas – This function uses New-TimeSpan and Get-Date to calculate the number of days until Christmas.  The output is something like “There are 10 days until Christmas!”

Here is the New-ChristmasTree function:

Here is the Get-DaysUntilChristmas function:

Here is a screenshot of the Get-DaysUntilChristmas function in action:

View original post

Adding organizational units to AD through powershell

Want to create some organizational structure in AD, I suppose it is specifically useful in a development environment, well the best solution is powershell then. Mastering Powershell by and Powershell.nu by Niklas Goude provide examples how to do this.

$domain = [ADSI]""
$ou = $domain.Create("organizationalUnit", "OU=Administration")
$ou.SetInfo()

Be sure you write “organizationalUnit” in lower case. Otherwise you’ll get “Exception calling “SetInfo” with “0” argument(s): “The specified directory service attribute or value does not exist” when you invoke $ou.setinfo().

If you want to create an OU under another OU, just create $domain and specify the location:

$domain = [ADSI]"OU=Administration, dc=takana, dc=local"
$company = $domain.Create("organizationalUnit", "OU=Accounting")
$company.SetInfo()</pre>
To save some other properties:
<pre>$ou.put("Description", "this is a dummy ou")
$ou.SetInfo()

Update 2013-12-10

In PowerShell V3 you have a built-in cmdlet for doing that once you add the Active Directory role in your server. It simple as that:

New-ADOrganizationalUnit "Accounting" -Path "dc=takana, dc=local"

Install a custom timer job in Powershell

First we have to create a class for our timer job which inherits SPTimerJobDefinition, build it and deploy it.

public class TakanaTimer : SPJobDefinition
{
	public TakanaTimer(){}
	public TakanaTimer(string jobName, SPService service,
		SPServer server, SPJobLockType targetType)
		: base(jobName, service, server, targetType) { }

	public TakanaTimer(string jobName, SPWebApplication webapp)
		: base(jobName, webapp, null, SPJobLockType.ContentDatabase)
	{
		Title = jobName;
	}
	public override void Execute(Guid contentDbId)
	{
		Log.Info("Running Takana Timer");
	}
}

There is a powershell example for this, but it has not been updated since MOSS.

In Powershell we need to load our custom assembly into powershell session:

[Reflection.Assembly]::LoadWithPartialName("Takana.Sharepoint.Timer")
$app = get-spwebapplication http://takana
$name = "takana-timer"
$job = new-object Takana.Sharepoint.Timer.TakanaTimer($name, $app)
$job.Schedule = [Microsoft.SharePoint.SPSchedule]::FromString("Every 15 minutes between 0 and 59")
$job.update()
#must restart timer service
restart-service sptimerv4

The cool thing here is also that we can create schedule from a text. The code is partially inspired by maraboustork

To remove a Timer Job, get the id of your timer job and run stsadm:

#Get the GUID of the timer job definition with the following cmdlet:
get-sptimerjob | sort-object -property Name | ft Name, Id
stsadm.exe -o deleteconfigurationobject -id

Or even better:

Get-SPTimerJob | where { $_.name -like “**” } |ft id,name
#Set job to a variable
$job = Get-SPTimerJob -id
#And delete it.
$job.Delete()

By the way, the whole list of ootb timers.

Вула Чăвашла

VulaCV - Чăвашла вулаттаракан сайт

Discovering SharePoint

And going crazy doing it

Bram de Jager - Coder, Speaker, Author

Office 365, SharePoint and Azure

SharePoint Dragons

Nikander & Margriet on SharePoint

Paul J. Swider - RealActivity

RealActivity is a specialized Microsoft healthcare services and solution advisory firm.

Cameron Dwyer | Office 365, SharePoint, Outlook, OnePlace Solutions

Office 365, SharePoint, OnePlace Solutions & Life's Other Little Wonders

paul.tavares

Me and My doings!

Share SharePoint Points !

By Mohit Vashishtha

Jimmy Janlén "Den Scrummande Konsulten"

Erfarenheter, synpunkter och raljerande om Scrum från Jimmy Janlén

Aryan Nava

| Blockchain | Serverless Architecture | Microservices Architecture | DevOps | AWS Lambda | Teraform |

SPJoel

SharePoint for everyone

SharePointRyan

Ryan Dennis is a SharePoint Solution Architect with a passion for SharePoint and PowerShell

SharePoint 2020

The Vision for a Future of Clarity

Aharoni in Unicode

Treacle tarts for great justice

... And All That JS

JavaScript, Web Apps and SharePoint

blksthl

Mostly what I know about SharePoint - CommunicoCuspis