Follow me on Twitter @AntonioMaio2

Tuesday, November 14, 2017

A Beginner's Guide to Administering Office 365 with PowerShell

With Office 365 PowerShell, you can manage Office 365 for your organization using commands and scripts that streamline your day to day work. Microsoft provides several easy to use admin centers to help manage Office 365. However, whether you’re an Office 365 administrator yourself or a service owner for Office 365 in your organization (working with other administrators), you’ll quickly find that you need to go beyond the capabilities that these admin centers provide. PowerShell can help you automate tasks so that they are easily repeatable, it can help you script management tasks so that they are automatically performed on a schedule and it can help you quickly output large amounts of data about your Office 365 environment. As well, some Office 365 settings are only manageable using PowerShell, with no UX provided. In this session, you’ll learn how to get started with Office 365 PowerShell and how to quickly become productive with it, making you more productive and empowered as you manage your Office 365 environment.

Thank you to those that attended my session today on this topic at SPTechCon DC! You were a great crowd with lots of great questions.

My updated slides can be found here:

Enjoy.
-Antonio

Monday, November 6, 2017

Set a SharePoint Online Managed Metadata (Taxonomy) Field Value on a Document or Item

Just last week I had an interesting little project I was helping a client with where we needed to use PowerShell to set 2 metadata field values for a document in a SharePoint Online Document Library. I've done this before many times. However, the challenge here was that the metadata fields were managed metadata fields, configured as part of a corporate taxonomy using the Managed Metadata Service.

My preferred method to accomplish this would have been SharePoint PnP PowerShell module using Set-PnPTaxonomyFieldValue (reference: https://msdn.microsoft.com/en-us/pnp_powershell/setpnptaxonomyfieldvalue). Unfortunately, for various reasons that wasn't possible in my client's environment so we had to resort to writing the PowerShell script ourselves.

As many of you know, you cannot set managed metadata field values the same way you would a regular metadata field. In searching the web for some guidance on which methods to use, I found that out of the blogs and documentation available, much of it was either incomplete or incorrect. So, with the help of a few kind folks on Twitter, namely Erwin Van Hunen (@erwinvanhunen) and Chris Kent (@thechriskent), I was able to work out a solution. I'd like to share here how that was accomplished so that others might benefit from Erwin's and Chris' assistance and from my experience. I'll try to be as complete as possible here, so that you have a full solution.

An important step is to download and install the latest SharePoint Online Client Components SDK. At the time of publishing, Microsoft had recently released a new version for September 2017, which can be downloaded from here: https://www.microsoft.com/en-ca/download/details.aspx?id=42038. This must be installed on the computer that will be running this script. Now onto our PowerShell code:

First, we'll set some basic variables:
$UserCredentials = Get-Credential
$webUrl = "https://mytenant.sharepoint.com/sites/mySiteCol/mySubsite/"
$listName = "MyList"
$itemToEdit = "/sites/mySiteCol/mySubsite/MyList/MyDocument.docx"
$termGroupName = "My Term Group"
$targetField1Name = "Field 1"
$targetField2Name = "Field 2"
$targetField1Value = "Value 1"
$targetField2Value = "Value 2"

Now, we setup our paths to the SharePoint Online Client Components SDK. Specifically, notice that we are using Microsoft.SharePoint.Client.Taxonomy.dll. These are the default paths where these DLLs should be installed on your computer.
$sCSOMPath = "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI"
$sCSOMRuntimePath=$sCSOMPath +  "\Microsoft.SharePoint.Client.Runtime.dll"
$sCSOMTaxonomyPath=$sCSOMPath +  "\Microsoft.SharePoint.Client.Taxonomy.dll"
$sCSOMPath=$sCSOMPath +  "\Microsoft.SharePoint.Client.dll"
Add-Type -Path $sCSOMPath
Add-Type -Path $sCSOMRuntimePath
Add-Type -Path $sCSOMTaxonomyPath

Next, we create our context and authenticate:
$context = New-Object Microsoft.SharePoint.Client.ClientContext($WebUrl)
$context.AuthenticationMode = [Microsoft.SharePoint.Client.ClientAuthenticationMode]::Default
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserCredentials.UserName, $UserCredentials.Password)
$context.Credentials = $credentials

Now, we retrieve our managed metadata fields from our Document Library and create our Taxonomy fields:
#get the collection of lists in our site and find our list by name
$lists = $context.web.Lists
$context.Load($lists)
$list = $lists.GetByTitle($listName)

#get our target field objects
$field1 = $list.Fields.GetByInternalNameOrTitle($targetField1Name)
$field2 = $list.Fields.GetByInternalNameOrTitle($targetField2Name)
$context.Load($field1)
$context.Load($field2)
$Context.ExecuteQuery()

$txField1 =[Microsoft.SharePoint.Client.ClientContext].GetMethod("CastTo").MakeGenericMethod([Microsoft.SharePoint.Client.Taxonomy.TaxonomyField]).Invoke($Context, $field1)
$txField2 = [Microsoft.SharePoint.Client.ClientContext].GetMethod("CastTo").MakeGenericMethod([Microsoft.SharePoint.Client.Taxonomy.TaxonomyField]).Invoke($Context, $field2)

Next, we create a session with the Managed Metadata Service and retrieve the terms we wish to set. This allows us to validate that the term values we're trying to set actually do exist in the term store.
$session = $spTaxSession = [Microsoft.SharePoint.Client.Taxonomy.TaxonomySession]::GetTaxonomySession($context)
$session.UpdateCache();
$context.Load($session)

$termStores = $session.TermStores
$context.Load($termStores)
$Context.ExecuteQuery()

$termStore = $TermStores[0]
$context.Load($termStore)
$Context.ExecuteQuery()

$groups = $termStore.Groups
$context.Load($groups)
$Context.ExecuteQuery()

$groupReports = $groups.GetByName($termGroupName)
$context.Load($groupReports)
$context.ExecuteQuery()

$termSetField1 = $groupReports.TermSets.GetByName($targetField1Name)
$termSetField2 = $groupReports.TermSets.GetByName($targetField2Name)
$context.Load($termSetField1)
$context.Load($termSetField2)
$context.ExecuteQuery()

$termsField1 = $termSetField1.GetAllTerms()
$termsField2 = $termSetField2.GetAllTerms()
$context.Load($termsField1)
$context.Load($termsField2)
$context.ExecuteQuery()

foreach($term1 in $termsField1)
{
    if($term1.Name -eq $targetField1Value)
    {
        Write-Host "Found our term in the termset: $($term1.Name) with id: $($term1.id)"
        break
    }
}

foreach($term2 in $termsField2)
{
    if($term2.Name -eq $targetField2Value)
    {
        Write-Host "Found our term in the termset: $($term2.Name) with id: $($term2.id)"
        break
    }
}

if(($term1.Name -ne $targetField1Value) -or ($term2.Name -ne $targetField2Value))
{
    Write-Host "Missing term set values.  Double check that the values you are trying to set exit in the termstore. Exiting."
    exit
}

Finally, find the item we want to edit, check it out (if file checkout is required), update the taxonomy metadata fields and check the document back in:

#get all items in our list
$listItems = $list.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery())
$context.Load($listitems)
$context.ExecuteQuery()  
$itemFound = $false

foreach($item in $listItems)  
{        
    If($($item["FileRef"]) -eq $ItemToEdit)
    {
        Write-Host "Found our item..."
        $itemFound = $true

        $context.Load($item.File) 
        $context.ExecuteQuery();

        #Checkout the item and assign the content type to the document
        if($item.File.CheckOutType -eq "None")
        {
            $item.File.CheckOut();
            Write-Host "Item has been checked out"
        }      

        #create a brand new field and set our Year managed metadata field
        #you cannot simply reuse the term object you found above in the term store
        $txField1value = New-Object Microsoft.SharePoint.Client.Taxonomy.TaxonomyFieldValue 
        $txField1value.Label = $term1.Name           # the label of your term 
        $txField1value.TermGuid = $term1.Id          # the guid of your term 
        $txField1value.WssId = -1                    # the default value

        $txField1.SetFieldValueByValue($item,$txField1value)

        #create a brand new field and set our Period managed metadata field
        #you cannot simply reuse the term object you found above in the term store
        $txField2value = New-Object Microsoft.SharePoint.Client.Taxonomy.TaxonomyFieldValue 
        $txField2value.Label = $term2.Name           # the label of your term 
        $txField2value.TermGuid = $term2.Id          # the guid of your term 
        $txField2value.WssId = -1                    # the default value

        $txField2.SetFieldValueByValue($item,$txField2value)

        #update the item with all changes
        $item.Update()
        $item.File.CheckIn("Item metadata values have been updated", 1)  

        Write-Host "Item has been checked in with updated metadata values"

        $context.ExecuteQuery();

        break;
    }
}

if($itemFound)
{
    Write-Host "Updating the item's metadata is complete."
}
else
{
    Write-Host "Item not found: $itemToEdit"
}

I'm sure there are ways to make some of this more efficient, and of course the most efficient method of all would be to use the SharePoint PnP PowerShell module using Set-PnPTaxonomyFieldValue (reference: https://msdn.microsoft.com/en-us/pnp_powershell/setpnptaxonomyfieldvalue) if you're environment allows it at the time. However, I wanted to paint a complete picture of what's involved in building such a script if you have to create it from scratch in PowerShell yourself.

Enjoy.
-Antonio