Wednesday, September 5, 2018

Start a workflow with Powershell - and pause for each item

This is one I use often.  It's pretty simple, but it allows me to start a workflow for all items in a list, and to wait between each item.  SharePoint is rickety - and delaying a kickoff of 400 workflows on 400 items is probably a good idea.  :)

Add-PSSnapin microsoft.sharepoint.powershell
# URL of the Site
$web = Get-SPWeb -Identity "http://somesharepointsite.com/sites/collection/subweb"
#Get workflow manager
$manager = $web.Site.WorkFlowManager
# Name of the list
$list = $web.Lists["Some SharePoint List"]
# Name of the Workflow
$assoc = $list.WorkflowAssociations.GetAssociationByName("Some Workflow","en-US")
$count = 0
$data = $assoc.AssociationData
$items = $list.Items
foreach($item in $items)
 {
    #Pause one minute between each workflow
    Start-Sleep -s 60
   
    #Fire the workflow for that item
    $wf = $manager.StartWorkFlow($item,$assoc,$data,$true)
 
    #Write to console that the workflow has fired
    write-host "Workflow fired for item: " $item.Name
 }
$manager.Dispose()
$web.Dispose()

Thursday, October 26, 2017

SiteServicesAddins.ReadFromPropertyBag: Site services addins don't exist for site collection

Oh man, this is an error that, when you see it, you'll be lost.  Here was out scenario:

Users would upload a document to a library in a Site Collection.  They would then try to tag that document with metadata.  We had four pieces of metadata, and the user would be able to tag them.

Then when the user clicked save, they would be told they don't have permission to update the document's properties, and two of the four metadata fields would not post. 

We would also see the following in the ULS logs:

CatalogConfig.ReadFromPropertyBag; Catalog configurations don't exist for site collection http://webapp/sites/collection

SiteServicesAddins.ReadFromPropertyBag: Site services addins don't exist for site collection http://webapp/sites/collection

At the same time, one of our admins recalled that they had deleted the Style Resource Readers group from the collection.

When that group is deleted, MANY BAD THINGS HAPPEN.  They are:

- Access Denied errors to a page
- Inability to browse menu items
- Inability to update metadata

So in our case, we re-created the Style Resource Readers group, gave NT Authority\Authenticated Users read permissions to the group, and made sure it had access to:

- The Style Library
- The Master Page Gallery
- The Site Assets Library

But still, we had the errors from above. 

After more digging, we found there is a list called the Hidden Taxonomy list that also is affected by this group.  This list holds Information Management Policy values when you set expiration on metadata.  The two fields the user couldn't update also happened to be fields that had expiration policies set on them.  So when SharePoint tried to invoke the policy to start it, the user didn't have rights to see what that policy was, hence the metadata was not applied.

That Hidden Taxonomy list lives at:  http://webapp/sites/collection/lists/hiddentaxonomylist

Once we went into that list and added the re-created Style Resource Readers group, we were back in business. 

The lesson here is be very careful who has Site Collection Admin rights, and make sure they speak loudly that they have maybe made a mistake.  It saved us a tremendous amount of time knowing a group was deleted that probably shouldn't have been. 

Then it was just an issue of finding something that was hidden. 

Wednesday, May 31, 2017

RBS ghosts in my house!

So let's say you deployed Remote Blob Storage for SharePoint.  Then later you decided you didn't want to deploy it for a particular content database and retracted the data from the blob back into the SQL content database.


Then later....maybe much later....uploads to your SharePoint site start failing.  Maybe it's after a Cumulative Update.  Maybe it's after a SQL upgrade.  You see the following in your ULS logs:

System.Runtime.InteropServices.COMException: The URL 'Documents/somedoc.docx' is invalid.  It may refer to a nonexistent file or folder, or refer to a valid file or folder that is not in the current Web.  


Failed in GetStreamInfo: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> Microsoft.Data.SqlRemoteBlobs.RemoteBlobUsageException: The specified blob store <1> could not be found in the current database. 


What do you do?


There are a couple solutions that work for other people, then there's the solution that worked for us:


1.  Ensure Remote Blob Storage is set up correctly.  https://technet.microsoft.com/en-us/library/ee748631.aspx


2.  Ensure the Application Pool account that hosts the Web App where your Content Database lives has the correct RBS permissions.


For us, the fix was actually ensuring the Content Database that was no longer using RBS was ACTUALLY not using RBS.  It got confused, and we had to straighten it out with Powershell.  The script for the fix was fairly straightforward:

add-pssnapin Microsoft.SharePoint.Powershell


$cdb=get-SPContentDatabase some_content_database
$rbs=$cdb.RemoteBlobStorageSettings
$rbs.SetActiveProviderName("")


***

Using this, the Active Provider for RBS was disabled.  As soon as we ran this, we were then able to upload files without trouble to the SharePoint sites that were operating in that Content Database.


Hope this helps someone!

Friday, April 7, 2017

SharePoint 2013 High Trust Apps - 401 Unauthorized Error When Calling User Profile Service

When you call the User Profile Service in SharePoint 2013 from a High Trust App (or a SharePoint Add-In, as they're calling them now) some interesting things happen.  Consider the following scenario:


- You have an App that shows some data for a user via a CSOM call.  It's essentially a search of data that everyone at your company can see. 
- You also have, in the same App, a tab that shows information specific to that user, filtered by the user's location attribute in the User Profile Service. 


When the page loads, the first tab loads fine, since there are no calls.  The second tab, however, doesn't load anything.  You'll also get something like this in the Event log of your App Hosted server:


Source: System; Error: The remote server returned an error: (401) Unauthorized.; Stack Trace: at System.Net.HttpWebRequest.GetResponse()

at Microsoft.SharePoint.Client.SPWebRequestExecutor.Execute()

at Microsoft.SharePoint.Client.ClientRequest.ExecuteQueryToServer(ChunkStringBuilder sb)


This is the App Hosted server saying "Hey, I tried to talk to the User Profile Service, but I was denied."


There are a...LOT....of reasons why the User Profile Service might deny a request.  It could be the Application Pool account of the request doesn't have access to query the User Profile service application.  It could be SQL database permissions. 


One of the ways we found to help troubleshooting was to isolate the User Profile service to a Web Front End.  That is - we started the service on a web front end, and stopped it everywhere else.  (NOTE:  This is NOT the User Profile Synchronization Service.  Touch that not, else your life will suck in new, interesting ways).


Once the UPS was running on a web front end, we were able to see the request, and the reply in the same ULS log.  With that, we saw this:


SPJsonWebSecurityBaseTokenHandler: Issuer name in token '5924ce70-6ed4-4d21-913d-23b43664342d@1dd693ac-dfc1-434b-8eba-8758c2922b8e' doesn't match any of the registered issuer names for trusted sts 'SharePoint High-Trust App Tenant UAT'.

What does that mean?  It means the sts 'SharePoint High-Trust App Tenant' for UAT don't know nothin' about any app using an IssuerId of 5924ce70-6ed4-4d21-913d-23b43664342d.


We went to our App Hosted server, checked our web.config file for the App in question, and sure enough, the IssuerId in our <appSettings> section was wrong.  The settings looked like this:

<add key="IssuerId" value="5924ce70-6ed4-4d21-913d-23b43664342d" />


We checked a different App in the same farm, and saw this for its settings:


<add key="IssuerId" value="df6429b9-e397-4fb8-b0d2-0429a82d68a1" />


Once we swapped to the correct IssuerId, everything worked.


But why?  The why here is the High Trust Tenant is what enabled the App Hosted Servers, which are essentially just IIS servers, to be trusted by SharePoint.  If the Tenant doesn't have an IssuerId that matches what the App is saying its IssuerId is, it'll get blocked.


So in this case, User Profile was fine.  Application Pools were fine.  The App Model was fine. 


The lesson is:  Don't let a developer have access to your UAT farm - else he might decide his new App needs a brand new IssuerId, and set the entire farm's config for his lone App.  And you might not notice it until your App does something that requires elevated permissions. 

Wednesday, November 16, 2016

Powershell to delete User Profile Properties

This Powershell script will let you remove a specific User Profile property without having to use Central Administration.  This can be handy in a few different scenarios.

Script is:

Add-PsSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue

$site = Get-SPSite "http://somesharepointsite.com"
$context = Get-SPServiceContext($site)
$upcm = New-Object Microsoft.Office.Server.UserProfiles.UserProfileConfigManager($context)   
$cm = $upcm.ConnectionManager                
$pdtc = $upcm.GetPropertyDataTypes()
$ppm = $upcm.ProfilePropertyManager
$cpm = $ppm.GetCoreProperties()
$ptpm = $ppm.GetProfileTypeProperties([Microsoft.Office.Server.UserProfiles.ProfileType]::User)
$psm = [Microsoft.Office.Server.UserProfiles.ProfileSubTypeManager]::Get($context)
$ps = $psm.GetProfileSubtype([Microsoft.Office.Server.UserProfiles.ProfileSubtypeManager]::GetDefaultProfileName([Microsoft.Office.Server.UserProfiles.ProfileType]::User))
$pspm = $ps.Properties

# Remove User Profile Property by Name
$cpm.RemovePropertyByName("NAMEOFPROPERTYTODELETE")

Tuesday, October 4, 2016

Get all groups & members for a Site Collection

Add-PsSnapin Microsoft.SharePoint.PowerShell

$siteUrl = "http://somesite/sites/collection"
$web = Get-SPWeb $siteUrl

@(foreach ($group in $web.SiteGroups) {
  foreach($user in $group.Users) {
    $usergroup = New-Object System.Object
    $usergroup | Add-Member -type NoteProperty -name GroupName -value $group.Name
    $usergroup | Add-Member -type NoteProperty -name UserName -value $user.Name

     Write-Output $usergroup
    }
}) | Export-Csv c:\userlist.csv -NoTypeInformation

Thursday, August 11, 2016

Powershell to edit a SharePoint Web Application's web.config file

You really want to do this instead of directly editing the web.config file.  If you change the web.config directly, SharePoint won't know it's actually changed.  During the next patch or Config Wizard, SharePoint will overwrite the web.config with what it thinks should be there, which is bad.

For our purposes, we store the config changes in a csv file that Powershell will run.  That config file holds things like cache timeout settings for some of our custom apps, a custom branding url, and a switch for debug mode to be enabled for specific apps.

That config file can contain anything you might want, but it will need three columns.  They are:  Name, Owner, and Value

Ours looks like this:


Name Owner Value
app.DebugMode itsp FALSE
app.BrandingUrl itsp http://somesite.com/sites/common
app.Cache.CacheTimeout itsp 1
app.Cache.IsCacheEnabled itsp FALSE
app.Lists.EventsList itsp Events Calendar
app.Lists.ContactsList itsp Contacts
app.Lists.LinksList itsp Local Links
app.Lists.AssetsList itsp Site Assets


The Powershell is:

# Change this variable to the target Web App
$webAppUrl = "http://somewebapp.com"
$CSVLocation = "F:\Temp\ConfigMods.csv"


# Add Snapin if it is not already loaded
if ((Get-PSSnapin | ? {$_.Name -eq "Microsoft.SharePoint.PowerShell"}) -eq $null)
{
    Add-PSSnapin "Microsoft.SharePoint.PowerShell"
}

# Import configuration
$mods = Import-CSV $CSVLocation

$webApp = Get-SPWebApplication $webAppUrl

# Get the mods that need to be checked
$ownerstodelete = $mods | Group-Object owner | Select-Object Name

# For each mod
foreach($owner in $ownerstodelete)
{
    # Create an array
    $modstodelete = @()

    # For each current web mod
    foreach($mod in $webApp.WebConfigModifications)
    {
        # If mod is marked
        if($mod.Owner -eq $owner.Name)
        {
            # Add to the array
            $modstodelete += $mod
        }
    }

    Write-Host "Removing $($modstodelete.Count) web.config modifications from $($webAppUrl)" -ForegroundColor Yellow

    # Foreach mode to be deleted
    foreach($delmod in $modstodelete)
    {
        # Delete the mod
        $webApp.WebConfigModifications.Remove($delmod) > $null
        Write-Host " + Deleted mod $($delmod.Value)" -ForegroundColor Red
    }
}

# Reset sequence
$i = 0;

Write-Host ""
Write-Host "Adding $($mods.Count) web.config modifications to $($webAppUrl)" -ForegroundColor Yellow

# For each mod to add
foreach($modEntry in $mods)
{
    # Create the mod value
    $modValue = [system.string]::format(“<add key=""{0}"" value=""{1}"" />", $modEntry.Name, $modEntry.Value)

    # Create the mod object
    $mod = New-Object Microsoft.SharePoint.Administration.SPWebConfigModification
    $mod.Path = "configuration/appSettings"
    $mod.Name = [string]::Format("add[@key='{0}']", $modEntry.Name)
    $mod.Sequence = $i++
    $mod.Owner = $modEntry.Owner
    $mod.Type = 0
    $mod.Value = $modValue

    # Add the mod to the web app
    $webApp.WebConfigModifications.Add($mod)
    Write-Host " + Added mod $($modValue)" -ForegroundColor Cyan
}

# Update the web app
$webApp.Update()

# Apply web config changes
$webApp.WebService.ApplyWebConfigModifications()

# Run all Admin timer jobs
net stop SPAdminV4 > $null
Start-SPAdminJob
net start SPAdminV4 > $null

Write-Host ""
Write-Host "Web.config changes have been applied successfully" -ForegroundColor Green