May 20, 2015

Media Center Status Application goes open source

Windows Media Center Status Application I mainly developed during the early stages of Windows Vista (also supports 7 and 8) is now open source. It was originally developed for my own use, but I soon realized there were hundreds if not thousands of Media Center users out there who wanted to share their media viewing preferences in social media, such as Facebook and Twitter.

During the evolution of the application many nice features were added, such as getting notifications of tweets of people you follow inside Media Center. It didn't take long until someone requested a way to filter out what items are published to Facebook wall (as it was called back then), so I also added RegEx filter to filter out specific media titles from getting sent out - if one had automatic publishing enabled.

There was also ambitious monetization ideas when I added feature that fetched media images (such as DVD and CD covers) from Amazon to show in the Facebook status update. I always knew that the user base for the application wasn't that huge and wasn't too surprised I didn't end up getting a dime from Amazon referrals. But I did get few hits so it proved it was working.

Otherwise monetization relied on donations, and I was surprised that I indeed got two donations totalling €25. That was €25 more than I was expecting, so thank you Marc Stride and Graham Evans. I'd like to think I bought beer with that money and enjoyed it on a sunny summer evening, but probably I just bought cement or boards for the house construction.

I got the idea for the application once during Microsoft's MSDN conference where I decided not to participate in the boring directly work related enterprise sessions, but instead go off-the-beaten-path. I learned so much not only about Media Center, but also on Robotics Studio. The next few evenings I spent at the hotel programming this application.

Biggest challenge was by no doubt the ever changing APIs of Facebook, Twitter. First the Facebook walls were gone and Graph API was introduced, then Twitter did lot of changes in authentication. Media Center development was the only one that didn't receive any major changes since Vista, unfortunately.

I was considering open sourcing it for a long time, but just didn't get to it, but hey, better later than never.

You can find bits at https://mcsa.codeplex.com.

Compiled app, changelog and other details are still at http://www.jussipalo.com/fbmce.

March 4, 2015

Sync any folder to OneDrive without moving it to local OneDrive folder

Problem

I’ve been using c:\work folder forever to store my work related semi-temporary files on my laptop hard drive. Although losing contents of that file due to hard drive issue or similar wouldn’t cause much downtime in my work (as all important files are stored in SharePoint Online), I still didn’t feel too relaxed with the possibility of losing even 5 minutes of work in case that folder somehow disappeared.

Few times I’ve decided to move my c:\work to OneDrive for Business folder in order to sync work related files automatically to business version of OneDrive hosted in Office 365. However, each time I’ve got frustrated by the fact that finding the OneDrive folder in File Explorer is so difficult. Sure, using Favorites or Quick Access (as in Windows 10), I can get there pretty easily, but still, it will add at least one additional folder level and lots of clikety clicks with my tired little mouse to get where I want to go. Plus, just looking at the left pane in File Explorer that is so full with garbage (several instances of OneDrive, DLNA devices just below This PC, Libraries, Control Panel?!, yet again same libraries, Desktop items at the root, etc.) makes me want to cry.

Solution

Use junction points to direct content from c:\work into your OneDrive folder, such as C:\Users\Jussi\OneDrive - Sulava Oy\Work. After creating junction point such as this, all content you add to c:\work will actually reside in the OneDrive folder, and are synced to OneDrive normally. And best of all, I can still use c:\work like I’ve always used.

Steps how to do this:

  1. Rename your existing c:\work (or whatever your folder is called) to e.g., c:\work_old
  2. Create Work folder into the OneDrive folder, e.g., C:\Users\Jussi\OneDrive - Sulava Oy\Work
  3. Open Command Prompt
  4. Type in mklink /j c:\Work "C:\Users\Jussi\OneDrive - Sulava Oy\Work"
  5. Move old content from c:\work_old to c:\work and confirm that Work folder under OneDrive folder starts syncing and eventually goes green

NOTE! As there is really just one instance of files on your hard drive, if you remove folder under OneDrive folder, it will also be removed from the junction point location.

January 27, 2015

Office365 PowerBI error: ‘oops, something went wrong’

Problem

Opening Office 365 PowerBI reports in a browser doesn’t work for some users. Instead of a working report, they get error “oops, something went wrong”, followed by descriptive error “[object Object]”. Error is raised from infonav.min.js.

Issue occurs on some users, regardless of what browser they are using. For some users it occurs only on IE, but not on Chrome.

image

Solution

Further investigation showed that the issue is due to users having increased the text size in Windows. With Chrome, you can use all text sizes except the largest one. With Internet Explorer, only the smallest text size works.

Please note that number of steps on the slider might differ depending on the resolution of your screen. If in doubt, go for the Smaller end, and increase one step at a time to find our the breaking point in your configuration.

image

December 11, 2014

SharePoint injects garbage at the end of Page Layout

Problem

I upload custom Page Layouts to SharePoint 2013 using CSOM and PowerShell. Uploading a page layout file and configuring it is fairly straightforward, but when creating a new publishing page using the uploaded page layout, and attempting to view the page, you get error:

Sorry, something went wrong
Only Content controls are allowed directly in a content page that contains Content controls.

image

Looking at the Page Layout file in SharePoint Designer, you see that there are indeed strange lines of code appended at the end of the file, outside Content controls, see lines 27-34 in picture below.

image

For some reason SharePoint has injected elements such as SharePointWebControls:ctfieldrefs, mso:CustomDocumentProperties, meta name="WebPartPageExpansion", SharePoint:CTFieldRefs but why? This issue only occurs on custom page layouts, with any default page layout you cannot reproduce the issue.

Solution

As you can see from the SPD picture above, you get the issue also with a very simple page layout, where you’ve basically stripped away everything custom. But wait, look at the <asp:content> nodes, notice how content is with lower case, and not Content.

YES! YES! YES!

Having asp:content with lower case will mess up SharePoint and it will inject strange lines at the end of your custom page layout. Replace them with asp:Content and issue no longer occurs.

How to set FileLeafRef when creating list items using CSOM

Problem

When using CSOM via PowerShell, and adding folder type list items, I needed to set also the Name field, a.k.a FileLeafRef field value for the item in order to be able to easily add children to items.

Setting FileLeafRef during initial item creation never worked.

Solution

First create the item, and after that change FileLeafRef.

 $listItemCreationInformation = New-Object Microsoft.SharePoint.Client.ListItemCreationInformation
 $listItemCreationInformation.FolderUrl = "http://site.com/lists/mylist/folder"
 $newItem = $list.AddItem($listItemCreationInformation);
    $title = "Item title"

  $newItem["Title"] = $title
 $newItem.Update();
 $clientContext.ExecuteQuery()

 $newItem["FileLeafRef"] = $title
 $newItem.Update();
 $clientContext.ExecuteQuery()

September 24, 2014

SharePoint: Replacing owners of Term Sets and Terms using PowerShell and CSOM

SCRIPT

Param (
    [Parameter(Mandatory=$True)]
    [string]$Url,
    [Parameter(Mandatory=$True)]
     [string]$OldOwner,
    [Parameter(Mandatory=$True)]
    [string]$NewOwner,
    [Parameter(Mandatory=$True)]
    [string]$AdminUsername
)

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Taxonomy")

## Stop here
$lcid = "1033"

Write-Host "Please enter password for $($Url):"
$pwd = Read-Host -AsSecureString

# connect/authenticate to SharePoint Online and get ClientContext object..
$sCtx = New-Object Microsoft.SharePoint.Client.ClientContext($Url)
$sCredentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($AdminUsername, $pwd)
$sCtx.Credentials = $sCredentials

if (!$sCtx.ServerObjectIsNull.Value)
{
    Write-Host "Connected to SharePoint Online: " $sCtx.Url "" -ForegroundColor Green

    $sTaxonomySession = [Microsoft.SharePoint.Client.Taxonomy.TaxonomySession]::GetTaxonomySession($sCtx)
    $sTaxonomySession.UpdateCache()
    $sCtx.Load($sTaxonomySession)
    $sCtx.ExecuteQuery()

    if (!$sTaxonomySession.ServerObjectIsNull)
    {
        Write-Host "Taxonomy session: " $sTaxonomySession.Path.Identity "" -ForegroundColor Green

        $sTermStore = $sTaxonomySession.GetDefaultSiteCollectionTermStore()
        $sCtx.Load($sTermStore)
        $sCtx.ExecuteQuery()

        if ($sTermStore.IsOnline)
        {
            Write-Host "Term Store connected:" $sTermStore.Id "" -ForegroundColor Green
            $sCtx.Load($sTermStore.Groups)
            $sCtx.ExecuteQuery()

            foreach ($sTermGroup in $sTermStore.Groups)
            {
                Write-Host "Term Group: " $sTermGroup.Name "-" $sTermGroup.Id "" -ForegroundColor Green
                $termSets = $sTermGroup.TermSets
                $sCtx.Load($termSets)
                $sCtx.ExecuteQuery()

                foreach($termSet in $termSets)
                {
                    Write-Host "Term Set found: " $termSet.Name ", Current Owner: " $termSet.Owner "" -ForegroundColor Green

                    If($termSet.Owner -match $OldOwner)
                    {
                        Write-Host "Replacing old termset owner!" -ForegroundColor Yellow
                        $termSet.Owner = $NewOwner
                    }

                    $terms = $termSet.Terms
                    $sCtx.Load($terms)
                    $sCtx.ExecuteQuery()
                    foreach($term in $terms)
                    {
                        Write-Host "Term found: " $term.Name ", Current Owner: " $term.Owner "" -ForegroundColor Green

                        If($term.Owner -match $OldOwner)
                        {
                            Write-Host "Replacing old term owner!" -ForegroundColor Yellow
                            $term.Owner = $NewOwner
                        }
                    }
                }               
            }

            #####
            Write-Host "Rolling back..." -ForegroundColor Cyan
            $sTermStore.RollbackAll()
            #Write-Host "Committing..." -ForegroundColor Cyan
            #$sTermStore.CommitAll()
            #####

            $sTermStore.UpdateCache()

            Write-Host "Done!" -ForegroundColor Green
        }
    }
}

 

USAGE

You need to uncommend the CommitAll command at the end, and comment the RollbackAll command in order for the script to store changes in database. Script will loop through all term groups and sets and terms and change owner of term sets and terms.

.\ReplaceTermSetOwner.ps1 -Url https://myspsite.sharepoint.com –OldOwner oldowner@myspsite.com -NewOwner newowner@myspsite.com -AdminUsername adminusername@myspsite.com

SharePoint: Change Term Set Owner using PowerShell via CSOM

SCRIPT

Param (
    [Parameter(Mandatory=$True)]
    [string]$Url,
    [Parameter(Mandatory=$True)]
    [string]$NewOwner,
    [Parameter(Mandatory=$True)]
    [string]$TermGroupName,
    [Parameter(Mandatory=$True)]
    [string]$TermSetName,
    [Parameter(Mandatory=$True)]
    [string]$AdminUsername
)
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Taxonomy")
$lcid = "1033"
Write-Host "Please enter password for $($Url):"
$pwd = Read-Host -AsSecureString
# connect/authenticate to SharePoint Online and get ClientContext object..
$sCtx = New-Object Microsoft.SharePoint.Client.ClientContext($Url)
$sCredentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($AdminUsername, $pwd)
$sCtx.Credentials = $sCredentials
if (!$sCtx.ServerObjectIsNull.Value)
{
    Write-Host "Connected to SharePoint Online: " $sCtx.Url "" -ForegroundColor Green
    $sTaxonomySession = [Microsoft.SharePoint.Client.Taxonomy.TaxonomySession]::GetTaxonomySession($sCtx)
    $sTaxonomySession.UpdateCache()
    $sCtx.Load($sTaxonomySession)
    $sCtx.ExecuteQuery()
    if (!$sTaxonomySession.ServerObjectIsNull)
    {
        Write-Host "Taxonomy session: " $sTaxonomySession.Path.Identity "" -ForegroundColor Green
        $sTermStore = $sTaxonomySession.GetDefaultSiteCollectionTermStore()
        $sCtx.Load($sTermStore)
        $sCtx.ExecuteQuery()
        if ($sTermStore.IsOnline)
        {
            Write-Host "Term Store connected:" $sTermStore.Id "" -ForegroundColor Green
            $sCtx.Load($sTermStore.Groups)
            $sCtx.ExecuteQuery()
            foreach ($sTermGroup in $sTermStore.Groups)
            {
                if ($sTermGroup.Name -eq $TermGroupName)
                {
                    Write-Host "Term Group: " $sTermGroup.Name "-" $sTermGroup.Id "" -ForegroundColor Green
                    $termSets = $sTermGroup.TermSets
                    $sCtx.Load($termSets)
                    $sCtx.ExecuteQuery()
                    $termSet = $termSets.GetByName($TermSetName)
                    $sCtx.Load($termSet)
                    $sCtx.ExecuteQuery()
                        Write-Host "Term Set found: " $termSet.Name ", Current Owner: " $termSet.Owner "" -ForegroundColor Green
                        $termSet.Owner = $NewOwner
                         Write-Host "Owner changed to: " $NewOwner "" -ForegroundColor Cyan
                }
            }
            Write-Host "Committing..." -ForegroundColor Cyan
            $sTermStore.CommitAll()
            $sCtx.ExecuteQuery()
            Write-Host "Done!" -ForegroundColor Green
        }
    }
}

USAGE

.\ChangeTermSetOwner.ps1 -Url https://myspsite.sharepoint.com -NewOwner newowner@myspsite.com -TermGroupName MyTermGroup -TermSetName MyTermSet -AdminUsername adminusername@myspsite.com