Browse Tag

Delve

Find and Report on Existing Delve Blogs

With the announcement that Delve blogs will be retiring you may want to see what blogs exist in your tenant. Delve blogs create their own site collection but do not show up in the admin center or when you use the SharePoint PowerShell module and the Get-SPOSite cmdlet. Thankfully PnP Powershell does return this. I put together a PowerShell script to find blogs and put a report together including the number of posts.

First, install PnP PowerShell if you haven’t already. I recommend installing via the PowerShell gallery with the command:

  • Install-Module SharePointPnPPowerShellOnline

Here is a script to find and export the blog information using PnP Powershell. Ensure you fill in your own variables for your tenant and the file path.

try {

    #variables -> enter your own domain and output path
    $creds = Get-Credential
    $tenantadmin = "https://domain-admin.sharepoint.com"
    $outputfilepath = "c:\temp\delveblogexport.csv"

    #connect to tenant to get blog sites
    Connect-PnPOnline $tenantadmin -Credentials $creds
    $sites = Get-PnPTenantSite -Template POINTPUBLISHINGPERSONAL#0

    $resultsarray = @()

    #loop through sites to get details for blog
    foreach($s in $sites){
        Connect-PnPOnline $s.Url -Credentials $creds
        $list = Get-PnPList -Identity "Pages"
        $pagecount = $list.ItemCount
        $listlastmodified = $list.LastItemUserModifiedDate
        $contributor = Get-PnPGroupMembers -Identity "Contributors" | select Email

        #add to export object
        $obj = New-Object PSObject
        Add-Member -InputObject $obj -MemberType NoteProperty -Name DelveBlogUrl -Value $s.Url
        Add-Member -InputObject $obj -MemberType NoteProperty -Name BlogPageCount -Value $pagecount
        Add-Member -InputObject $obj -MemberType NoteProperty -Name LastModified -Value $listlastmodified
        Add-Member -InputObject $obj -MemberType NoteProperty -Name Email -Value $contributor.Email

        $resultsarray += $obj
        $obj = $null

        Disconnect-PnPOnline

    }
    #export results
    $resultsarray | Export-Csv -Path $outputfilepath -NoTypeInformation
    Write-Host "Complete" -ForegroundColor Green
}
catch
{
    Write-Host $_.Exception.Message -ForegroundColor Red
}

The results will include the URL of the site, the page count, last modified, and the email of the blog site owner.

If you want other details per page you can go directly to the pages library to view by applying “pPg/Forms/AllItems.aspx” to the blog site url. As an example:

When you go to the pages library you can download the posts. They exist in a JSON blob. This may be a good way to extract blog posts before they are removed via Microsoft.

To view the posts you will still go through “portals/hub/personal/drew” path vs “portals/personal/drew”.


Another path to get some of this information is through the User Profiles that exist. Each user profile includes a link to their Delve blog. So if you get all existing user profiles you can find where that value is filled in. The best way to get this at scale is through SharePoint search. I put together a script to do this as well. I included batching logic on the results which will be needed in large tenants as the max search results is only 500.

try
{
    #variables -> enter your own domain and output path
    $creds = Get-Credential
    $tenantadmin = "https://domain-admin.sharepoint.com"
    $outputfilepath = "c:\temp\delvebloguserprofileexport.csv"
    $returnproperties = @("PreferredName","AccountName","WorkEmail")
    $sourceid = "B09A7990-05EA-4AF9-81EF-EDFAB16C4E31"  #this is consistent across tenants
    $maxresults = 100
    $startrow = 0

    #connect to tenant to search
    Connect-PnPOnline $tenantadmin -Credentials $creds

    $resultsarray = @()
    Do{
   
        #perform search query
        $results = Submit-PnPSearchQuery -Query "*" -SourceId $sourceid -SelectProperties $returnproperties -StartRow $startrow -MaxResults $maxresults -SortList @{LastModifiedTime="Descending"} 
        $rowcount = $results.RowCount

        #loop through results in row
        foreach($res in $results.ResultRows){

            #get user profile properties
            $props = Get-PnPUserProfileProperty -Account $res.AccountName

            #check if blog site exists
            if($props.UserProfileProperties.'SPS-PointPublishingUrl' -ne ""){

                #add to export object
                $obj = New-Object PSObject
                Add-Member -InputObject $obj -MemberType NoteProperty -Name DelveBlogUrl -Value $props.UserProfileProperties.'SPS-PointPublishingUrl'
                Add-Member -InputObject $obj -MemberType NoteProperty -Name WorkEmail -Value $props.UserProfileProperties.WorkEmail
                $resultsarray += $obj
                $obj = $null
            }
        }
        $startrow = $startrow + $rowcount + 1
    }
    while ($rowcount -ne 0)
    
    #export results
    $resultsarray | Export-Csv -Path $outputfilepath -NoTypeInformation
    Write-Host "Finished" -ForegroundColor Green
}
catch
{
    Write-Host $_.Exception.Message -ForegroundColor Red
}

This is not the most efficient way to get this information but it could be helpful to double check the SharePoint sites approach. This is also a handy way to loop through user profiles via search.

Removing Permissions for Viewing Modern Personal Blogs in Office 365

A personal blog can be a great tool for you to contribute your thoughts and ideas. Office 365 provides the capability for everyone to have a personal blog that can be accessed via your profile page. 

When you create a new blog post this will be automatically be view-able by all employees. If you do not want to have this capability or manage this in any way it can be done via PowerShell. The example I put together will remove viewers access from all existing blogs so they can only be seen by the owner.

To get started we need a high level understanding of what these blogs are and how they work. I won’t go into all of the details of this because Benjamin Niaulin has already put it together in this great post:

The highlights to support this post are:

  • When a user follows the links to create a new blog post a new site collection is built with the managed path of /portals/personal with a site name of your user account
    • i.e. tenant.sharepoint.com/portals/personal/dmadelung
    • These are not viewable in any SP Admin center and Get-SPOSite will not work
  • Site collections are only built after a user initiates the creation so not all users will have one
  • Blog posts (stories) are creates at pages in the pages library on your site collection
  • Permissions are handled with SharePoint permissions and inherited down with a Contributors, Creators, and Viewers SharePoint Group
    • The viewers group includes “Everyone except external users” by default
  • The blogs are NOT deleted when a user leaves like their OneDrive site collection

And here are details the details from Microsoft around personal blog posts in Office 365:

Removing existing permissions via PowerShell

As this is all hosted in SharePoint there could be multiple ways that we can control these. Unfortunately I couldn’t find a way to control things as scale but there is a small UserVoice submission for it. What I wanted to ensure was that creators could still get to their content but no one else could view anything. The path I took to manage these was through PowerShell and CSOM (Client Side Object Model)

Here is link to the GitHub repo and I will break it down below along with the script.

Here are some key things to note:

  • I can not confirm that doing this is the best practice but it was the easiest way I found to control these without a any administrator controls available to us. 
  • This is currently built to run on demand but could be updated to run on a schedule via something like Azure Automation.
    • To catch everything it will need to run on a schedule because any future sites will not be caught.
  • This could be updated to be used as a reporting tool or identification tool for cleanup.
  • I would comment out the actual removal of the permissions and put some logging in to test before fully running.
    • Also if you have any changes please update the repo!
  • This queries the user profile service in SharePoint Online to get the full list of users which could be huge.
    • I didn’t test this on a very large environment so this could take awhile to run or need to be enhanced for scale.
  • All of the user profile gathering was copied from this post from Microsoft on how to display a list of OneDrive for Business site collections

To get started with CSOM & PowerShell with SharePoint Online here is a good blog post from Chris O’Brien. You can get the latest version of SharePoint Online CSOM here. If you download the nuget file you can change the file extension to .zip and extract the .dlls.

To utilize the script make sure you fill out the appropriate variables and more information about what this will do is below the script. Make sure you test any script you get online before you really run it!

# Use this script to remove viewer permissions from all user delve blogs that have been created
# A user will still be able to view their existing blogs and create blogs but people will not be able to see them
# This would allow you to choose in the future if you want to make them live
# 
# This could be updated to run on a schedule as this will not remove any new blogs that are created

### ENTER YOU VARIABLES HERE ###

#Path to the SP CSOM files 
$csomPath = "C:\...." 
################

#Prompt for parameters
#TenantDomain is beginning of "tenantdomain.sharepoint.com.."
$TenantDomain = Read-Host -Prompt "Tenant domain"
$AdminAccount = Read-Host -Prompt "Admin account"
$AdminPass = Read-Host -Prompt "Password for $AdminAccount" –AsSecureString

#Set SharePoint admin url
$AdminURI = "https://" + $TenantDomain + "-admin.sharepoint.com"

#Get CSOM files
Add-type -Path "$csomPath\Microsoft.SharePoint.Client.dll"
Add-type -Path "$csomPath\Microsoft.SharePoint.Client.Runtime.dll"

#Begin the process
$loadInfo1 = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
$loadInfo2 = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")
$loadInfo3 = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.UserProfiles")

#Set credentials for CSOM
$creds = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($AdminAccount, $AdminPass)

#Add the path of the User Profile Service to the SPO admin URL, then create a new webservice proxy to access it
$proxyaddr = "$AdminURI/_vti_bin/UserProfileService.asmx?wsdl"
$UserProfileService= New-WebServiceProxy -Uri $proxyaddr -UseDefaultCredential False
$UserProfileService.Credentials = $creds

#Set variables for authentication cookies
$strAuthCookie = $creds.GetAuthenticationCookie($AdminURI)
$uri = New-Object System.Uri($AdminURI)
$container = New-Object System.Net.CookieContainer
$container.SetCookies($uri, $strAuthCookie)
$UserProfileService.CookieContainer = $container

#Sets the first User profile, at index -1
$UserProfileResult = $UserProfileService.GetUserProfileByIndex(-1)

Write-Host "Starting- This could take a while."

#Getting total number of profiles
$NumProfiles = $UserProfileService.GetUserProfileCount()
$i = 1

#Create array to track users
$users = @()

#As long as the next User profile is NOT the one we started with (at -1)...
While ($UserProfileResult.NextValue -ne -1) 
{
    Write-Host "Reviewing profile $i of $NumProfiles"

    #Look for the Point Publishing Blog url object in the User Profile and retrieve it
    #It will be empty for users which it has not been created for

    #Get personal blog publishing URL
    $Prop = $UserProfileResult.UserProfile | Where-Object { $_.Name -eq "SPS-PointPublishingUrl" } 
    $Url= $Prop.Values[0].Value

    #Get user UPN - Can be used for reporting
    #$Prop = $userProfileResult.UserProfile | Where-Object { $_.Name -eq "SPS-UserPrincipalName"}
    #$Upn= $Prop.Values[0].Value

    #If the blog site exists then add it to an array to review
    if ($Url) {
        $users += $Url
    }

    #And now we check the next profile the same way...
    $UserProfileResult = $UserProfileService.GetUserProfileByIndex($UserProfileResult.NextValue)
    $i++
}

#Loop through all identified sites to remove blog viewers
foreach($user in $users){
    #Set blog site url
    $siteurl = "https://" + $TenantDomain + ".sharepoint.com" + $user

    #Connect to blog site collection
    $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteurl)
    $ctx.Credentials = $Creds
 
    #Connect to web and get site groups
    $web = $ctx.Web
    $groups = $ctx.Web.SiteGroups
    $ctx.Load($web)
    $ctx.Load($groups)
    $ctx.ExecuteQuery()
    
    #Get the viewers group
    $group = $groups | where { $_.Title -eq "Viewers"}
    if($group){
        #Get the users in the viewers group
        $users = $group.Users
        $ctx.Load($users)
        $ctx.ExecuteQuery()

        #Remove all users from the viewers group
        foreach($u in $users){
            $group.Users.RemoveByLoginName($u.LoginName)
            $web.Update()
            $ctx.ExecuteQuery()
        }
    }
}

The end result will be that all existing blog sites will have anyone in the Viewers SharePoint Group removed

Before…

After..

Steps to hide content from Delve in Office 365

When Delve was released by Microsoft it was pretty groundbreaking in the content management world. There was finally a tool that would help people find content no matter where it was located. This is one of the primary struggles that I work with clients with all the time. People love to think that adding custom managed metadata and building a super complex information architecture will allow people to find content easier. In some use cases this works but as a daily business practice this can drive people away. And once you break down those ECM walls you end up with unorganized chaos. As an ECM guy my palms get sweaty just thinking about some of the setups I have worked on where millions of files are strewn across file shares with no good way to ever find anything. So when Delve came along and basically said; “I don’t care where your content is – here are the files that I think you should be using.” this lessened the need of an in-depth ECM process and at the same time opened up people’s eyes around content security. I don’t want to go too much into Delve as a whole but here is some quick info and handy links before I break down how to hide content.

What is Office Delve?

  • Delve helps you discover the information that’s likely to be most interesting to you right now – across Office 365. Find information about people – and through people – and help others find you.
  • You don’t have to remember the title of a document or where it’s stored. Delve shows you documents no matter where they’re stored in OneDrive for Business or in Sites in Office 365.
  • Delve never changes any permissions, so you’ll only see documents that you already have access to. Other people will not see your private documents.
  • Office Delve for Office 365 admins

Hiding content from Delve

You may have a scenario in which you don’t want content to appear in Delve no matter what. Some examples could be high volume transactional processing files or payroll tracking information. Thankfully Microsoft has given us a simple way to hide content from Delve by using a site column called HideFromDelve. This can be done at the library level as well but I wanted to provide instructions so end users can have the power to add this column after it has been created once.

Important note: This will only hide content from the Delve app itself. The document will still be viewable via search and the Office Graph.

  1. Navigate to the site that has a library of files in which you want to be able to hide them from Delve
  2. Use the gear in the top right to go to Site Settings

  3. Under Web Designer Galleries, click on Site columns

  4. Click Create


  5. Enter the column information and click Ok, the name and type have to be EXACT for this to work.
    1. Name: HideFromDelve
    2. Type: Yes/No (checkbox)
    3. Default value: No


  6. Navigate to the library that you want to hide content and go to Library Settings via the ribbon


  7. Under the list of columns, click the link Add from existing site columns

  8. Scrolls down in the Available site columns list and select the newly created HideFromDelve column and click the add button to move it across. Then click Ok

  9. Navigate back to your library and you can begin marking documents to be hidden from Delve. The quickest way to do this is through the Quick Edit view.

  10. After the next scheduled crawl, the document will no longer appear in Delve!