Script to Download Yammer Image Profile

Here’s a neat script I wrote the other day to download the urls of all the user’s in your Yammer network. This comes in handy when you want to re-use the profile images that users have uploaded to Yammer.

The advantage with this script is that you don’t need the app to be approved by your Yammer admin. Just create a user created app on Yammer, create a token and approve it for your profile. That’s it. The token can be re-used as long as necessary.

Here’s the PowerShell script that will get you there.

<#
.SYNOPSIS
    This script downloads the profile image urls of all the users from Yammer, creates a csv file and uploads it to the YammerProfile document library.
    Example>    .\Download-YammerProfile.ps1 -YammerToken 05fdIb1pNwIFEBg9k34EWw -SiteUrl http://mysharepoint/site
#>
param(
    [Parameter(Mandatory = $True, HelpMessage='Yammer Access Token - Refer to https://developer.yammer.com/authentication/ for how to generate an access token. Eg. 05fdIb1pNwIFEBg9k34EWw')]
    [string]$YammerToken,
    [Parameter(Mandatory = $True, HelpMessage='The url of the site that will store the Yammer user profile list. E.g. http://mysharepoint/site')]
    [string]$SiteUrl
)

Function Main() {
    $documentLibrary = "YammerProfiles"
    $csvFile = "YammerProfiles.csv"

    CreateEventLogSource "MePage"
    Log "Starting Download-YammerProfile.ps1"
    try {
        $spWeb = Get-SPWeb -Identity $SiteUrl
        CreateSharePointLibrary $spWeb $documentLibrary  "Downloaded cache of Yammer profile image urls" "DocumentLibrary"
        CreateYammerProfileCsv $YammerToken $csvFile
        $spWeb = Get-SPWeb -Identity $SiteUrl #Refresh reference to avoid timeout issue when it takes a long time to download from Yammer
        UploadToSharePoint $csvFile $spWeb $documentLibrary

        Log "Download-YammerProfile.ps1 completed succesfully"
    }
    catch {
        [string]$errDetail = $_.ToString()
        LogError "Error occured: $errDetail"
        Write-Error $_
    }
}

Function CreateYammerProfileCsv($token, $csvFile){
    $page = 0
    $userCount = 0
    if (Test-Path $csvFile){ Remove-Item $csvFile }
    do {
        $page++     
        $users = GetYammerUsers $token $page
        $userCount = $users.Length
        Log "Downloading Yammer Profiles Page: $page Users: $userCount"
        for($i = 1; $i -lt $users.Length; $i++) {
            $user = $users[$i];         
            if($user.mugshot_url_template.EndsWith("no_photo.png")) { continue } #Only store valid photos           
            for($e = 0; $e -lt $user.contact.email_addresses.Length; $e++) {                
                $email = $user.contact.email_addresses[$e];
                if($email.type -eq "primary"){ #We're only interested in the telstra.com email addresses    
                    $output = $email.address.ToLowerInvariant() + "," + $user.mugshot_url_template
                    Write-Host $user.mugshot_url_template $email.address
                    Add-Content $csvFile $output
                }
            }
        }
        Start-Sleep -s 2 #Sleep for two seconds so we don't go over Yammer's rate count of 10 req / 10 sec
    }
    while ($userCount -gt 0) # -and $page -eq 1)
}

Function UploadToSharePoint($fileName, $spWeb, $documentLibraryName) {
    Log "Uploading $fileName to $spWeb/$documentLibraryName"
    #Read the contents into a memory stream to prevent the file getting locked
    [byte[]]$byteArray = Get-Content -encoding byte -path $fileName 
    [void][reflection.assembly]::LoadWithPartialName("System.IO")
    $stream = [System.IO.MemoryStream]$byteArray

    $spDoc = $spWeb.GetFolder($documentLibraryName)
    $spFile = $spDoc.Files.Add($fileName, $stream, $true)
}

Function CreateSharePointLibrary ($Web, $LibraryName, $Description, $LibraryTemplate) {  
    $spListCollection = $spWeb.Lists   
    $spLibrary=$spWeb.Lists.TryGetList($LibraryName)
    if($spLibrary -eq $null) {
        Log -f Yellow "$LibraryName document library does not exist. Creating..."
        $spListCollection.Add($LibraryName, $Description, $LibraryTemplate)
    }
}

Function LoadPSSnapIn {
    $snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'}
    if ($snapin -eq $null) {
        Write-Host "Loading SharePoint Powershell Snapin"
        Add-PSSnapin "Microsoft.SharePoint.Powershell"
    }
}

Function ConvertToJson($jsonString) {
    [System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")
    $ser = New-Object System.Web.Script.Serialization.JavaScriptSerializer
    return $ser.DeserializeObject($jsonString)
}

Function GetYammerUsers($token, $pageNumber) {
    $users = InvokeYammerGetRequest $token "users.json?page=$pageNumber"
    return $users
}

Function InvokeYammerGetRequest($token, $target) { 
    # Setup the request
    $yammerApiUrl = "https://www.yammer.com/api/v1/"
    $requestUrl = $yammerApiUrl + $target
    $webRequest = [System.Net.WebRequest]::Create($requestUrl)
    $webRequest.Method = "GET"
    $webRequest.Headers.Add("Authorization", "Bearer $token")

    # Execute the request
    [System.Net.WebResponse]$resp = $webRequest.GetResponse()
    $reqstream = $resp.GetResponseStream()
    $sr = new-object System.IO.StreamReader $reqstream
    $obj = ConvertToJson $sr.ReadToEnd()
    return $obj
}

Function CreateEventLogSource($sourceName) {            
    try {
        if(![System.Diagnostics.Eventlog]::SourceExists($sourceName))
        {
            Write-Host "Creating event log source $sourceName"
            New-Eventlog -LogName "Application" -Source $sourceName
        }
    }
    catch {
        Write-Host "Error creating event log source $sourceName"            
        Write-Host $_
    }
}

Function Log($message) {
    Write-Host $message
    WriteEventLog $message $false
}

Function LogError($message) {   
    WriteEventLog $message $true    
}

Function WriteEventLog($message, $isError) {
    try {
        if($isError) {
            Write-EventLog -logname Application -source MePage -eventid 4002 -EntryType Error -Message $message
        }
        else {
            Write-EventLog -logname Application -source MePage -eventid 4001 -EntryType Information -Message $message
        }
    }
    catch { #We don't want the script to error out because it couldn't write to event log       
        Write-Host "Error writing message to eventlog. Message = $message"
        Write-Host $_
    }
}

$ErrorActionPreference = "stop"
LoadPSSnapIn
return Main
Script to Download Yammer Image Profile

Creating PDF files dynamically with PowerShell

So how does one go about creating PDF files dynamically? You need a pinch of the open source PdfSharp library and 10 lines of PowerShell.

Add-Type -Path .\PdfSharp.dll
$doc = New-Object PdfSharp.Pdf.PdfDocument
$doc.Info.Title = "Created dynamically"
$page = $doc.AddPage()
$gfx = [PdfSharp.Drawing.XGraphics]::FromPdfPage($page)
$font = New-Object PdfSharp.Drawing.XFont("Verdana", 20, [PdfSharp.Drawing.XFontStyle]::BoldItalic)
$msg = "Hello World"
$rect = New-Object PdfSharp.Drawing.XRect(0,0,$page.Width, $page.Height)
$gfx.DrawString($msg, $font, [PdfSharp.Drawing.XBrushes]::Black, $rect, [PdfSharp.Drawing.XStringFormats]::Center)
$doc.Save("HelloWorld.pdf")
Creating PDF files dynamically with PowerShell

Creating jUnit/xUnit compatible xml test tesults in PowerShell

I originally wrote this function when I needed to create jUnit/xUnit compatible test results. The resulting xml file was then fed into the jUnit plugin in Jenkins to show the results PowerShell/PSUnit based tests.

Function Write-JunitXml([System.Collections.ArrayList] $Results, [System.Collections.HashTable] $HeaderData, [System.Collections.HashTable] $Statistics, $ResultFilePath)
{
$template = @'
<testsuite name="" file="">
<testcase classname="" name="" time="">
    <failure type=""></failure>
</testcase>
</testsuite>
'@

    $guid = [System.Guid]::NewGuid().ToString("N")
    $templatePath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), $guid + ".txt");

    $template | Out-File $templatePath -encoding UTF8
    # load template into XML object
    $xml = New-Object xml
    $xml.Load($templatePath)
    # grab template user
    $newTestCaseTemplate = (@($xml.testsuite.testcase)[0]).Clone()  

    $className = [System.IO.Path]::GetFileNameWithoutExtension($HeaderData.TestFileName)
    $xml.testsuite.name = $className
    $xml.testsuite.file = $HeaderData.TestFileName

    foreach($result in $Results) 
    {   
        $newTestCase = $newTestCaseTemplate.clone()
        $newTestCase.classname = $className
        $newTestCase.name = $result.Test.ToString()
        $newTestCase.time = $result.Time.ToString()
        if($result.Result -eq "PASS")
        {   #Remove the failure node
            $newTestCase.RemoveChild($newTestCase.ChildNodes[0]) | Out-Null
        }
        else
        {
            $newTestCase.failure.InnerText = Format-ErrorRecord $result.Reason
        }
        $xml.testsuite.AppendChild($newTestCase) > $null
    }   

    # remove users with undefined name (remove template)
    $xml.testsuite.testcase | Where-Object { $_.Name -eq "" } | ForEach-Object  { [void]$xml.testsuite.RemoveChild($_) }
    # save xml to file
    Write-Host "Path" $ResultFilePath

    $xml.Save($ResultFilePath)

    Remove-Item $templatePath #clean up
}
Creating jUnit/xUnit compatible xml test tesults in PowerShell

Making SharePoint JSon calls from PowerShell in SharePoint 2013

The Invoke-RestMethod in PowerShell 3 doesn’t send the appropriate header required by SharePoint 2013 to return JSon results. It took me a while to figure out this workaround.

$parameter = "my search text"
$encParam = [System.Web.HttpUtility]::UrlEncode("'$parameter'")  
$url = "http://mysp2013site/_api/search/query?querytext=$encParam"  
$wc = new-object System.Net.WebClient  
$wc.UseDefaultCredentials = $true  
$wc.Headers.Add("Accept", "application/json; odata=verbose")  
$res = $wc.DownloadString($url)  
$res
Making SharePoint JSon calls from PowerShell in SharePoint 2013

FBA Configuration Manager for SharePoint 2010

Setting up forms based authentication in SharePoint 2010 requires making changes in three web.config files.

This utility allows you to perform the update in a single click and was inspired by the configuration manager that Steve Peschka released.

I’ve taken a different approach to Steve’s, instead of using a feature and a timer job I directly update the config files in the local machine. To update the other machines in the farm you can use the included PowerShell script.

The utility makes a back-up of all of the web.configs before updating them. I’ve also included the sample membership and role providers that Steve provided.

The PowerShell script to perform the update uses the same engine as the UI.


function global:Get-ScriptDirectory()
{
$Invocation = (Get-Variable MyInvocation -Scope 1).Value
Split-Path $Invocation.MyCommand.Path
}

$webApp = Get-SPWebApplication http://localhost:9191
$settingsPath = Join-Path (Get-ScriptDirectory) "SqlMembershipConfig.xml"
$settings = Get-Content $settingsPath
$scriptDir = Get-ScriptDirectory
$assemblyPath = Join-Path $scriptDir "FBA.dll"
Add-Type -Path $assemblyPath
[FBA.ConfigureMembershipProvider]::Configure($webApp, "Default", $settings)

The source code and release packages are available in CodePlex: http://fbaconfigmanager.codeplex.com/

FBA Configuration Manager for SharePoint 2010