Home

SharePoint 2016

Create subsite with PowerShell and Task Scheduler in SharePoint Server 2016

posted Dec 7, 2016, 2:58 AM by Benny Skogberg

This is a common scenario, which I’ve come across several times. You have a list of subsites to be created. It contains all the properties needed to create the subsite, but you don’t know how to do it. Either your stuck with creating a Workflow using Visual Studio or SharePoint Designer. If you use SharePoint Designer, you can use REST API. But it’s limited to 50 properties, which you will reach soon if your creating unique permissions, custom groups, adding users, and custom template. I’ve tried this approach for a couple of weeks, and it’s a daunting task. Further, the workflow manager farm or the workflow itself is pain to work with. It works on Monday, but not on Tuesday. So, I’ve decided to take this “creating subsite” task out of the workflow and are using PowerShell instead.

To me, it’s better, since I’m more skilled with PowerShell – and once you get it to work – it never fails. Never!

So, here’s what we’ll do in our script. Create a subsite that has a 
  • Custom template
  • Unique permissions (doesn’t inherit permission from the root web)
  • Adding groups and permissions 
  • Adding users to groups
In plain English, this script creates a subsite, owner, member and visitor group. Set permission on each group and add one site owner and several members – all based on my Custom List of Subsites.

The Task Scheduler
Create a new Task, where you make sure you trigger the 32-bit version of PowerShell which lives in System32 subfolders.

General

Trigger

Action

The action can be tricky to get right. First see too that you run with bypass executionpolicy and point to your PowerShell script.

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
-executionpolicy bypass -file C:\tasks\New-SPWeb-CustomTemplate.ps1

The script mentioned above is quite long, so don’t just copy-paste. Make sure you use your values in variables and fields.

The Script
if ($ver.Version.Major -gt 1) {$host.Runspace.ThreadOptions = "ReuseThread"} 
if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) 
{
     Add-PSSnapin "Microsoft.SharePoint.PowerShell"
}

# Instance variables
$web = Get-SPWeb "http://SiteCollection"
$list = $web.Lists["Project"]
$fields = $list.fields
$items = $list.items
$subsite = $null
$createWeb = $false
$webs = Get-SPSite "http://SiteCollection" | Get-SPWeb

foreach($item in $items)
{
    if($item["subsiteCreated"] -eq $false)
    {
        Write-Host $item["SubsiteURL"] $item["subsiteCreated"] $item["Projektledare"]
        $subsite = $item
        $createWeb = $true

        # If we find an item, we use that one and skip the rest until next run
        break
    }
}

# To see wether or not there alredy is an subsite on the same URL
$newWeb = "http://SiteCollection" + $item["SubsiteURL"]

#Create a subsite from a new item if it's not already present
if($createWeb -and $newWeb -notin $webs.Url)
{
    ### Create subsite ###
    # If we use the -template property when we use New-SPWeb, the script fails. That's why we need to apply template
    # after the subsite is created
    Write-Host "Creating Web" $item["SubsiteURL"] -ForegroundColor Green
    $mainurl = "http://SiteCollection" + $subsite["SubsiteURL"]
    $template = $web.GetAvailableWebTemplates(1053) | Where-Object {$_.Title -eq "subsiteWeb"}
    $web = New-SPWeb $mainurl -Language 1053 -Name $subsite["SubsiteURL"] -UniquePermissions -UseParentTopNav 
    $web.ApplyWebTemplate($template.Name)
    
    
    ### Create Owner Group ###
    Write-Host "  Creating Owners group" -ForegroundColor Yellow
    $groupName = "Owner of " + $subsite["SubsiteURL"]
    $ownerName = $item["Projektledare"].split("#")
    $memberName = $item["Projektledare"].split("#")
    $description = "Owner of subsite " + $item["SubsiteURL"]

    if ($web.SiteGroups[$groupName] -ne $null)
    {
        throw "Group $groupName already exists!"   
    }

    $op = New-SPClaimsPrincipal $item["ProjectManager"] -IdentityType WindowsSamAccountName
    $mp = New-SPClaimsPrincipal $item["ProjectManager"] -IdentityType WindowsSamAccountName
    $users = $web | Get-SPUser

    foreach($user in $users)
    {
        if($user.DisplayName -eq $ownerName[1])
        {
            $op = $user
        }
        if($user.DisplayName -eq $memberName[1])
        {
            $mp = $user
        }

    }

    $owner = $web | Get-SPUser $op
    $member = $web | Get-SPUser $mp

    # Add user to group
    Write-Host "  Adding user to group" -ForegroundColor Yellow
    $web.SiteGroups.Add($groupName, $owner, $member, $description)
    $SPGroup = $web.SiteGroups[$groupName]
    $web.RoleAssignments.Add($SPGroup)
    $web.Update()

    # Set permission on group
    Write-Host "  Setting permissions on group" -ForegroundColor Yellow
    $role = $web.RoleDefinitions["Full Control"]
    $RoleAssignment = New-Object Microsoft.SharePoint.SPRoleAssignment($SPGroup)
    $RoleAssignment.RoleDefinitionBindings.Add($role)
    $web.RoleAssignments.Add($RoleAssignment)
    $web.Update()


    ### Create Member Group ###
    Write-Host "  Creating Member group" -ForegroundColor Yellow
    $groupName = "Medlemmar i " + $subsite["SubsiteURL"]
    $ownerName = $item["ProjectManager"].split("#")
    $memberNames = $item["Projektdeltagare"].User.UserLogin
    $description = "Medlemmar i projektet " + $item["SubsiteURL"]

    if ($web.SiteGroups[$groupName] -ne $null)
    {
        throw "Group $groupName already exists!"   
    }

    $owner = $web | Get-SPUser $op
    $member = $web | Get-SPUser $memberNames[0]

    # Add user to group
    Write-Host "  Adding user to group" -ForegroundColor Yellow
    $web.SiteGroups.Add($groupName, $owner, $null, $description) # Setting members to null, since we'll add them later
    $SPGroup = $web.SiteGroups[$groupName]
    $web.RoleAssignments.Add($SPGroup)
    $web.Update()

    # Add more users to group
    foreach($memberName in $memberNames)
    {
        $member = $web | Get-SPUser $memberName
        $SPGroup.AddUser($member)
    }
    $web.Update()

    # Set permission on group
    Write-Host "  Setting permissions on group" -ForegroundColor Yellow
    $role = $web.RoleDefinitions["Contribute"]
    $RoleAssignment = New-Object Microsoft.SharePoint.SPRoleAssignment($SPGroup)
    $RoleAssignment.RoleDefinitionBindings.Add($role)
    $web.RoleAssignments.Add($RoleAssignment)
    $web.Update()


    ### Create Visitor Group, with no users ###
    Write-Host "  Creating Visitor group" -ForegroundColor Yellow
    $groupName = "Visitor in " + $subsite["SubsiteURL"]
    $ownerName = $item["ProjectManager"].split("#")
    $description = "Visitor in subsite " + $item["SubsiteURL"]

    if ($web.SiteGroups[$groupName] -ne $null)
    {
        throw "Group $groupName already exists!"   
    }

    # Add user to group
    Write-Host "  Adding group" -ForegroundColor Yellow
    $web.SiteGroups.Add($groupName, $owner, $null, $description)
    $SPGroup = $web.SiteGroups[$groupName]
    $web.RoleAssignments.Add($SPGroup)
    $web.Update()

    # Set permission on group
    Write-Host "  Setting permissions on group" -ForegroundColor Yellow
    $role = $web.RoleDefinitions["Read"]
    $RoleAssignment = New-Object Microsoft.SharePoint.SPRoleAssignment($SPGroup)
    $RoleAssignment.RoleDefinitionBindings.Add($role)
    $web.RoleAssignments.Add($RoleAssignment)
    $web.Update()

   
    $item["subsiteCreated"] = $true
    $item.Update();
    $web.Dispose();
}





Initialize-SPResourceSecurity and IISRESET fix inaccessible new Term Store

posted Oct 19, 2016, 12:43 AM by Benny Skogberg

We've all seen this in a new farm, and it's annoying.

The Managed Metadata Service or Connection is currently not available. The Application Pool or Managed Metadata Web Service may not have been started. Please Contact your Administrator.

And so far I've been unable to solve this, permanently. You check the application pool, services.msc, services on server, database login user mappings, and everything is OK. But today I saw this comment on the post SharePoint Error : The Managed Metadata Service or Connection is currently not available. The Application Pool or Managed Metadata Web Service may not have been started

It says: 

Robin Willems
August 19, 2015 at 1:08 am
I was getting "Failed to get term store for proxy ‘Managed Metadata Service Application Proxy’. Exception: System.Security.SecurityException: Requested registry access is not allowed"

It was fixed by Running Sharepoint Management Shell (Powershell) and Type

Initialize-SPResourceSecurity <enter>

After that i did an IISRESET /noforce and it was fixed.

Without having much hope I run:
PS C:\> Initialize-SPResourceSecurity
PS C:\> IISRESET

Attempting stop...
Internet services successfully stopped
Attempting start...
Internet services successfully restarted
PS C:\>

Reload the Term Store - and it just worked. One wonder why this isn't initiated by default after creating a new service application? Anyway - this does the trick when installing using AutoSPInstaller in a SharePoint Server 2016 Farm.

Reference:
Initialize-SPResourceSecurity

Add custom tile to Office 365 App Launcher

posted Oct 18, 2016, 12:50 AM by Benny Skogberg

So you're a global admin in your Office 365 tenant, and your exited to add custom tiles to the app launcher. So you follow the four top rated guides from your google search which all says basically the same [1] [2] [3] [4]. Just follow the link to "View all my apps" which is supposed to be located at the bottom of the app launcher.

As it turns out, this isn't the case anymore. Today we can see the Get More Apps link instead, leading to a totally different page.
Get More Apps
 
Frustrating as this may be, the real URL is https://portal.office.com/myapps. There is another solotion, which isn't described too well. But you have another option. In the new App Launcher experience, you can see all your apps in the App Launcher using the link ALL.

All Apps

At the bottom of this screen (you may need to scroll) you see the Other apps section where your custom apps live. From there you can pin the app to your Home screen of the App Launcher, which is what you see when you click on the start of the App Launcher. Pin it to home, and all is solved. Both of these methods work the same.

Pin Custom App To Home Screen


Auditing in SharePoint 2013/2016

posted Sep 21, 2016, 3:40 AM by Benny Skogberg

Site Collection Auditing

I’ve heard that Auditing shouldn’t be used, because it fills your Content database with a lot of data you don’t need. And I’ve participated in project switching off auditing to free up disk space. But that’s often related to database auditing which typically logs every action by every user. And on an active site – that’s a lot.

But a scenario I’ve come across recently is what happens when a user deletes a document? In some organizations you need to track that there actually was a document and that it was rightfully deleted. You can’t do that with normal versioning technique, since versioning only works if the file is present (obviously).

Now every Site Collection in SharePoint 2013/2016 have auditing settings which is very granular. You don’t want edited or added documents (or list items), you just need deleted document – and that’s where site collection auditing comes into play. Turn your head to Site Settings and in Site Collection Administration you have Site collection auditing setting and Audit log reports. 
Site Settings - Site Collection Administration
Using auditing setting you determine what to track and how often you need this logs to be saved. Monthly is a good choice in most cases. What’s important to remember is that you want your audit report logs in your site collection and not on the system drive. You can quickly run into trouble if you save logs on the system drive and eating disk space or if you for whatever reason need to switch servers, your audits will be gone. That’s why we should use a Document Library which we name something like AuditLogs. But we don’t want our members or owners of the site to access the audit logs, just the farm admin or IT-personnel. We solve this by breaking permission inheritance, remove everyone and only add the farm account and the group of IT-professionals I’m sure you have in your organization.

In the audit log settings, we hit the browse button to point to our newly created AuditLog library. When we have our AuditLog document library present, we need to set what we want to track. As a test, we can track items being deleted (and restored). 

Configure Audit Log Settings

Audit Documents and items

That’s it! We’ve set a location, and what type of action we want to track – and how often we want the report to be created. At this stage we need to test our Auditing. So head over to a document library, and delete some documents. Preferably documents you’ve uploaded that doesn’t contain much. We don’t want to lose data just to prove Auditing.

Deleting Documents

And with the documents deleted, we turn back to site settings and hit Audit Log Reports. When we’re there we the Deletion link and press OK. If everything works as expected, the Audit Log is now present in our Audit Log document library.

Audit Report
Opening the spreadsheet, we’ll see both an overview and the details on different sheets.
Audit report overview

Audit report detailed

And that concludes the powerful auditing of SharePoint 2013/2016. Enjoy!

SPFx - the first steps

posted Aug 29, 2016, 10:51 AM by Benny Skogberg   [ updated Aug 29, 2016, 10:53 AM ]

Starting with SharePoint Framework can be a daunting task. Especially since one may be new to TypeScript, Node, Gulp and Yeoman which are preferred parts of the framework. In essence SPFx can be a great step for other developers to start working with SharePoint, since you don't need a local SharePoint environment. The only thing you need is a developer tenant of Office 365, which comes for free for up to a year. You could develop locally as well, but I find it easier to work with the developer tenant directly since you have a real Office 365 instance already in place and don't need to use (and produce) Mockup data.

But lets leave the tools for a minute. Just accept the fact that they work, and that you have used scaffolding with Yeoman ‘yo @microsoft/sharepoint’ and started your HelloWorld project with ‘gulp serve’. That’s all we need for now. If not, head over to the source HelloWorld WebPart, and come back here when your done. 


So now you have your HelloWorld webpart running both locally and on your development tenant. But if we want to add another property in the property pane? That's quite simple, since everything (so far) is in the same .ts file. First, at the top add the PropertyPaneCheckbox in the import clause of @microsoft/sp-client-preview. When done, we can add the checkbox in our webpart property. I added three just for fun so my group now looks like this:

              groupFields: [
                PropertyPaneTextField('description', {
                  label: strings.DescriptionFieldLabel
                }),
                PropertyPaneCheckbox('valid', {
                  text: 'Valid'
                }),
                PropertyPaneCheckbox('governed', {
                  text: 'Managed by IT'
                }),
                PropertyPaneCheckbox('hr', {
                  text: 'Only visible for HR'
                })
              ]

And here comes the cool part. When I save my .ts file - gulp (which is still running) makes funny noises (OK, no sound provided in PowerShell which I use) with verbose messages. It recompliles your webpart according to your request.

Reloading the webpart at my development tenant, I see the webpart change instantly. I don't even have to delete it and add it again (like old times). Everything just works Now. Suddenly, programming is fun again when you don't have to wait for hooking up to your environment. I just works, and my previous changes is still there. 

SPFx new webpart properties

An exception of type Microsoft.SharePoint.Administration.SPUpdatedConcurrencyException was thrown

posted Nov 23, 2015, 12:34 AM by Benny Skogberg   [ updated Aug 20, 2016, 6:55 AM ]

When you run the command PSConfig.exe -cmd upgrade -inplace b2b -wait and get an error, you often solve it by running Get-SPProduct -local to ensure that the the server is in sync.

The error message you get is

11/23/2015 08:34:40  1  ERR          Failed to upgrade SharePoint Products.
An exception of type Microsoft.SharePoint.Administration.SPUpdatedConcurrencyException was thrown.  Additional exception information: An update conflict has occurred, and you must re-try this action. The object SPUpgradeSession Name=Upgrade-20151123-083423-444 was updated by DOMAIN\SP_Farm, in the PSCONFIG (5092) process, on machine SERVER.  View the tracing log for more information about the conflict.
Microsoft.SharePoint.Administration.SPUpdatedConcurrencyException: An update conflict has occurred, and you must re-try this action. The object SPUpgradeSession Name=Upgrade-20151123-083423-444 was updated by DOMAIN\SP_Farm, in the PSCONFIG (5092) process, on machine SERVER.  View the tracing log for more information about the conflict.
   at Microsoft.SharePoint.Administration.SPConfigurationDatabase.StoreObject(SPPersistedObject obj, Boolean storeClassIfNecessary, Boolean ensure)
   at Microsoft.SharePoint.Administration.SPPersistedObject.BaseUpdate()
   at Microsoft.SharePoint.Upgrade.SPUpgradeSession.Update()
   at Microsoft.SharePoint.Upgrade.SPUpgradeSession.ContinueOnLocalThread(Guid id, Boolean consoleOutput)
   at Microsoft.SharePoint.PostSetupConfiguration.UpgradeTask.Run()
   at Microsoft.SharePoint.PostSetupConfiguration.TaskThread.ExecuteTask()

You may get stuck in a position where you still can't upgrade. Then you have to expand your PSConfig command into this:

PSConfig.exe -cmd upgrade -inplace b2b -wait -force -cmd applicationcontent -install -cmd installfeatures -cmd secureresources

This command solved the error in my 6-tier farm with 2 WFE, 2 APP and 2 SQL Servers mirrored and belonging to AlwaysOn Availability Group. As a side note - I have to remove the UsageAndHealth-DB from the availability group before upgrading SharePoint.

Find the FIM Client path

posted Nov 16, 2015, 6:10 AM by Benny Skogberg

If you have the two FIM services running (Forefront Identity Manager Service & Forefront Identity Manager Synchronization Service) you can start the Forefront Identity Manager 2010, which you use to manage synchronization i detail. The path to the FIM Client is:

C:\Program Files\Microsoft Office Servers\15.0\Synchronization Service\UIShell\miisclient.exe

FIM Client 2010

There is a compatibility range mismatch between the Web server and database "WSS_Content"

posted Sep 9, 2015, 11:41 PM by Benny Skogberg   [ updated Sep 11, 2015, 1:28 AM ]

This an annoying error message which can happen in various scenarios. I got this one trying to assign a SPWeb object. I knew the SPWeb existed since it was the root site collection. The complete error message was:

There is a compatibility range mismatch between the Web server and database "WSS_Content", and connections to the data have been blocked to due to this incompatibility. This can happen when a content database has not been upgraded to be within the compatibility range of the Web server, or if the database has been upgraded to a higher level than the web server. The Web server and the database must be upgraded to the same version and build level to return to compatibility range.

Searching the web get many answers, but none that linked to the source. There isn’t any reference (yet) on SharePoint 2013, but it works just as well for 2013 as well. The command-line you run is

PSConfig.exe -cmd upgrade -inplace b2b -wait

Script Output

Running the GUI-version of “SharePoint 2013 Products Configuration Wizard” doesn’t help, so you really need to run the script using SharePoint 2013 Management Shell as administrator. But you need to do so with your SP_Install or SP_Farm account which shouldn’t be local administrator on the machine. To overcome this problem temporarily add SP_Install or SP_Farm as local administrator and proceed with the script. You may need to run the script several times, as the script does a lot of magic on your content database. I needed four times to make it work all the way without warnings. All four upgrade sequences need to be successful before you’re done.

The -cmd is a mandatory parameter where you specify which action you want to perform. Here we’re using the upgrade command to fix our error of compatibility mismatch.

With the –inplace parameter we specify whether to use version to version upgrade or build to build. This can be quite confusing since when we look at our Configuration database version in the Servers in Farm page in Central administration (<CentralAdminURL:Port>/_admin/FarmServers.aspx) and it specifies a build number on SharePoint Foundation 2013 only. Every time you install a Cumulative Update (CU) or Public Update (PU) you need to run PSConfig. Most of the blogs use b2b parameter value, because you are patching the server, not changing version (i.e. SharePoint 2010 to SharePoint 2013). So we’re using the b2b parameter value in this scenario. See SharePoint Build Number vs Version? for more information.

Using –wait parameter is important to use since it specifies that SP Management Shell doesn’t return until the upgrade process is completed. And we want to wait and see that our failing content database is really upgraded.

There may be things to do after the upgrade i successful. Please check out the log:

Successful result script output

Reference: PSConfig command-linereference (SharePoint Server 2010)

WARNING: The user hasn't logged on to mailbox Discovery Search Mailbox

posted Sep 2, 2015, 12:03 PM by Benny Skogberg   [ updated Sep 3, 2015, 12:27 PM ]


When we want to know the total storage of all the mailboxes we have in Office 365, we are encouraged to use PowerShell and Exchange Online cmdlets. Before we get to the state where we can run scripts against our Exchange Online we have used the scripts to do so.

#Connect to Office 365
$credential = Get-Credential
Import-Module MSOnline
Connect-MsolService -Credential $credential

#Connect to Exchange Online
$exchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $credential -Authentication "Basic" –AllowRedirection

Import-PSSession $exchangeSession -DisableNameChecking

And we run the script against the mailbox – just to be informed that it doesn't work as expected.

Get-Mailbox -ResultSize Unlimited | Get-MailboxStatistics | Select DisplayName, StorageLimitStatus, TotalItemSize, TotalDeletedItemSize, ItemCount, DeletedItemCount | Export-CSV "C:\Temp\Mailboxes.csv" –NoTypeInformation

The output is something like this:

WARNING: The user hasn't logged on to mailbox 'DiscoverySearchMailbox{D919BA05-46A6-415f-80AD-7E09334BB852}'('d907f6dc-f370-4e48-b1e6-28eaa0375409'), so there is no data to return. After the user logs on, this warning will no longer appear.

So you struggle and try to sign in to the Discovery Search Mailbox. I even tried to create rules in mine, just to nudge it a bit and maybe something would happen?! But I have an Office 365 Business Essential subscription, and needs an Exchange Online plan 2 to create rules – so back to square one.

Instead of this – we need to filter out the Discovery Search Mailbox when getting the real mailbox data. Instead of using the above script, you can use the following which also provides a warning in the end – but the benefit is that you get the data in your csv-file.

$mailboxes = Get-Mailbox -ResultSize Unlimited

foreach ($mailbox in $mailboxes)
 {
     if($mailbox.DisplayName -ne "Discovery Search Mailbox")
     {
         Write-Host $mailbox.DisplayName
         $mailbox | Get-MailboxStatistics | Select DisplayName, StorageLimitStatus, TotalItemSize, TotalDeletedItemSize, ItemCount, DeletedItemCount | Export-CSV "C:\Temp\Mailboxes.csv" –NoTypeInformation –Append
     }
 }

And you have the result to open up in MS Excel and modify to your liking.

Service Connection – Configure Service Application Associations by adding a member to a custom group of connections in SharePoint Server 2010.

posted Sep 2, 2015, 12:00 PM by Benny Skogberg

If you have an existing named proxy group of Service Connections, where you want to add members (new connections to new services in a service farm), you need to fire up PowerShell to add new members (connections). At first we need to know the name of the service application proxy group. This is done by a fairly simple PS-command.

Get-SPServiceApplicationProxyGroup | select Name

Next you copy the name of the Service Application Group that you want to add a new member to, and replace the <SA Group Name> with the real name.

$sapg = Get-SPServiceApplicationProxyGroup | Where-Object {$_.Name -eq "<SA Group Name>"}

Now you have a reference to the custom group, and need to get the proxy you want to add as a new member.

Get-SPServiceApplicationProxy | select DisplayName, Id

Copy the Id of the ServiceApplicationProxy you want to add and use it as the -Identity parameter

$sap = Get-SPServiceApplicationProxy -Identity 53f7b267-de00-4b7d-a12c-0fb505e94669

Finally add your Proxy to the Proxy Group

Add-SPServiceApplicationProxyGroupMember -Identity $sapg -Member $sap

Check the Web Application Service Connection, and verify that you have successfully added the new member

1-10 of 20