PowerShell or Command Prompt, Change Folder

For the new breed of SharePoint developers, and using VS-Code for many projects, and even PowerShell, I’m jumping into a command window A LOT these days.

Click the Windows key

Type ‘cmd’ or ‘powershell’

Often, I need to switch over to a specific folder – and type in :

cd “C:\folder\whatever client\project name”

OR – Copy the folder address from Windows Explorer :

image

And then have to paste the address (right-mouse-click) – not forgetting quotes :

image

BUT – there’s a great quick shortcut to this – and it’s AWESOME.

Really.   Awesome.

Within the Windows Explorer bar – just type “PowerShell” (or “cmd”)

Hit ENTER on the keyboard

image

And – you instantly get a PowerShell window – starting in the same folder.image

Same works for CMD – how amazing is that !! 

Smile

Advertisements

Deploy Azure Function, using PowerShell

One of my favourite parts of AZURE is the way you can incorporate a set of PowerShell, in a callable wrapper – or via a Timer – aka, scheduled task.

You can then call this from within a Microsoft Flow – in Office 365 – or have a timer that then awakens, and does something.

My function is simple enough – it logs in to SharePoint, and checks a document library – and then emails a combined reminder list – once a day.

Deploying is easy – when you have access to the Azure tenant – but for a SCRIPTED deployment, I had a bunch of tasks to get through :

  1. Coffee
  2. Create Azure Resource Group – ie. a bucket for all the elements
  3. Create Azure Storage Account
  4. Create Azure Function App
  5. Update some Azure Function App Settings
  6. Upload the Azure Function code (PowerShell)
  7. Upload the MODULE files also
  8. Beer

** I can’t help with step #1 (long black or “Americano”) – or #8 (Sample, or 150 Lashes) – but here’s some tips/code for the other bits.

Smile 

1. Coffee (and login)

Execute the command to connect to Azure – you get a dialog box for username/password :

2. Create Azure Resource Group

image

3. Create Azure Storage Account

image

4. Create Function App

image

5. Update Azure Function Settings

Some of these are necessary for the operation of the Azure Function – others are just CUSTOM for my use/needs – eg. connecting to Office 365 has a User/Password + URL

image

6. Upload Azure Function – from PowerShell file

The contents of the PS1 file are loaded, and the set in the “props” when adding a new Azure Resource – I’m using a timer trigger – you could also do a HTTP trigger.

image

7. Upload the MODULE files for the Azure Function

I used an updated version of the code within this blog post – to push files into the “modules” folder – which is needed for SharePoint Client / CSOM and such.

http://blog.octavie.nl/index.php/2017/03/03/copy-files-to-azure-web-app-with-powershell-and-kudu-api

I have a subfolder called “modules” – with the actual files.    (eg. C:\dev\modules)

image

8. Beer

Well, before we do that – here’s the entire script – using a heap of Azure RM PowerShell functions – you’ll need to install those, if you don’t have them.

Here’s a list of all the commands I’ve used :

  • Get-AzureRmResource
  • Get-AzureRmResourceGroup
  • Get-AzureRmStorageAccountKey
  • Get-PublishingProfileCredentials
  • Invoke-AzureRmResourceAction
  • New-AzureRmResource
  • New-AzureRmResourceGroup
  • New-AzureRmStorageAccount
  • Set-AzureRMWebApp
  • Test-AzureName

Source Code

Hope this works for you – it was a fun process to piece it all together :

$location = ‘Australia Southeast’

$resourceGroupName = ‘rgqwerty’

$storageAccount = ‘saqwerty’

$functionAppName = ‘faqwerty’

$functionName = ‘azurefunctionqwerty’

$SourceFile = ‘sourcefile.ps1’

# =========================================================================

$resourceGroup = Get-AzureRmResourceGroup | Where-Object { $_.ResourceGroupName -eq $resourceGroupName }

if ($resourceGroup -eq $null)

{

New-AzureRmResourceGroup -Name $resourceGroupName -Location $location -force

}

# =========================================================================

if (!(Test-AzureName -Storage $storageAccount))

{

New-AzureRmStorageAccount -ResourceGroupName $resourceGroupName -AccountName $storageAccount -Location $location -SkuName “Standard_LRS”

}

# =========================================================================

$functionAppResource = Get-AzureRmResource | Where-Object { $_.ResourceName -eq $functionAppName -And $_.ResourceType -eq ‘Microsoft.Web/Sites’ }

if ($functionAppResource -eq $null)

{

New-AzureRmResource -ResourceType ‘Microsoft.Web/Sites’ -ResourceName $functionAppName -kind ‘functionapp’ -Location $location -ResourceGroupName $resourceGroupName -Properties @{} -force

}

# =========================================================================

$keys = Get-AzureRmStorageAccountKey -ResourceGroupName $resourceGroupName -AccountName $storageAccount

$accountKey = $keys | Where-Object { $_.KeyName -eq “Key1” } | Select Value

$storageAccountConnectionString = ‘DefaultEndpointsProtocol=https;AccountName=’ + $storageAccount + ‘;AccountKey=’ + $accountKey.Value

$AppSettings = @{}

$AppSettings = @{‘AzureWebJobsDashboard’ = $storageAccountConnectionString;

‘AzureWebJobsStorage’ = $storageAccountConnectionString;

‘FUNCTIONS_EXTENSION_VERSION’ = ‘~1’;

‘WEBSITE_CONTENTAZUREFILECONNECTIONSTRING’ = $storageAccountConnectionString;

‘WEBSITE_CONTENTSHARE’ = $storageAccount;

‘CUSTOMSETTING1’ = ‘CustomValue1’;

‘CUSTOMSETTING2’ = ‘CustomValue2’;

‘CUSTOMSETTING3’ = ‘CustomValue3’}

Set-AzureRMWebApp -Name $functionAppName -ResourceGroupName $resourceGroupName -AppSettings $AppSettings

# =========================================================================

$baseResource = Get-AzureRmResource -ExpandProperties | Where-Object { $_.kind -eq ‘functionapp’ -and $_.ResourceType -eq ‘Microsoft.Web/sites’ -and $_.ResourceName -eq $functionAppName }

$SourceFileContent = Get-Content -Raw $SourceFile

$functionFileName = ‘run.ps1’

#schedule – run every 1am every day

$props = @{

config = @{

‘bindings’ = @(

@{

‘name’ = ‘myTimer’

‘type’ = ‘timerTrigger’

‘direction’ = ‘in’

‘schedule’ = ‘0 0 1 * * *’

}

)

}

}

$props.files = @{$functionFileName = “$SourceFileContent”}

$newResourceId = ‘{0}/functions/{1}’ -f $baseResource.ResourceId, $functionName

# now deploy the function itself

New-AzureRmResource -ResourceId $newResourceId -Properties $props -ApiVersion 2015-08-01 -force

# =========================================================================

function Get-PublishingProfileCredentials($resourceGroupName, $webAppName)

{

$resourceType = “Microsoft.Web/sites/config”

$resourceName = “$webAppName/publishingcredentials”

$publishingCredentials = Invoke-AzureRmResourceAction -ResourceGroupName $resourceGroupName -ResourceType $resourceType

-ResourceName $resourceName -Action list -ApiVersion 2015-08-01 -Force

return $publishingCredentials

}

function Get-KuduApiAuthorisationHeaderValue($resourceGroupName, $webAppName)

{

$publishingCredentials = Get-PublishingProfileCredentials $resourceGroupName $webAppName

return (“Basic {0}” -f [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((“{0}:{1}” -f

$publishingCredentials.Properties.PublishingUserName, $publishingCredentials.Properties.PublishingPassword))))

}

function UploadFile($kuduApiAuthorisationToken, $functionAppName, $functionName, $fileName, $localPath )

{

$kuduApiUrl = “https://$functionAppName.scm.azurewebsites.net/api/vfs/site/wwwroot/$functionName/modules/$fileName”

$result = Invoke-RestMethod -Uri $kuduApiUrl `

-Headers @{“Authorization”=$kuduApiAuthorisationToken;”If-Match”=”*”} `

-Method PUT `

-InFile $localPath `

-ContentType “multipart/form-data”

}

# =========================================================================

$accessToken = Get-KuduApiAuthorisationHeaderValue $resourceGroupName $functionAppName

$moduleFiles = Get-ChildItem ‘modules’

$moduleFiles | % {

Write-Host “Uploading $($_.Name) … ” -NoNewline

UploadFile $accessToken $functionAppName $functionName $_.Name $_.FullName

Write-Host -f Green ” [Done]”

}

Change SharePoint Browser Tab Icon – FAVICON using jQuery

Within SharePoint, you can set the “FAVICON” to a specific url within the MASTER PAGE, by updating the HTML markup.

<link rel=”shortcut icon” href=”/_layouts/15/images/favicon.ico?rev=23” type=”image/vnd.microsoft.icon” id=”favicon” />

BUT – what if you don’t want to change the master page ?   

(which, you SHOULDN’T do, for Office 365)

A simple jQuery update will do it :

$(document).ready(function () {
     //change the FAVICON
     $(‘#favicon’).attr(‘href’, ‘/sites/GLOBAL/SiteAssets/IMG/favicon.ico’);
});

Create SPWeb in Office 365, using Custom Site Template (PowerShell)

When you need to create a LOT of SharePoint subsites, you can do a script with PowerShell – using the Microsoft.SharePoint.Client.WebCreationInformation class/object.

The steps are basically :

  • Connect to O365
  • Define the settings for the new WebSite
  • Add to WEBS
  • Context.ExecuteQuery

Here’s the PowerShell I’m using (see below to copy+paste) :

image

The tricky part – for a CUSTOM WEB TEMPLATE is knowing what “ID” to use.

Ordinarily, the STS#0 will create a team site – but how to get the ID for my custom site template ??

There’s a quick tip – via the UI – when you go to create a new SUB SITE – using the F12 developer tools.    See that <option> tag ??

<option value=”{24ECC832-383F-4C5E-B658-9A640EA73509}#Client Site Template”>Client Site Template</option>

image

*THAT* is the value you need to include – in the PowerShell as above…

And then you just call the “CreateSubSite” function – and stamp ‘em out.

Smile

======================================

$urlAdmin = “https://AVENGERS-admin.sharepoint.com

$user = “TONY.STARK@AVENGERS.com”

$password = “PepperPottsIsAwesome!”

$urlSite = “https://AVENGERS.sharepoint.com/sites/clients”

$passwordSecureString = ConvertTo-SecureString -string $password -AsPlainText -Force

$credential = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $user, $passwordSecureString

Connect-SPOService -Url $urlAdmin -Credential $credential

$spoCredentials = New-Object -TypeName Microsoft.SharePoint.Client.SharePointOnlineCredentials -argumentlist $user, $passwordSecureString

$spoCtx = New-Object Microsoft.SharePoint.Client.ClientContext($urlSite)

$spoCtx.Credentials = $spoCredentials

$spoCtx.RequestTimeout = “500000”

function CreateSubSite($title)

{

$WCI = New-Object Microsoft.SharePoint.Client.WebCreationInformation

$WCI.WebTemplate = “{24ECC832-383F-4C5E-B658-9A640EA73509}#Client Site Template”

$WCI.Description = $title

$WCI.Title = $title

$WCI.Url = $title

$WCI.Language = “1033”

$spoCtx.Web.Webs.Add($WCI)

$spoCtx.ExecuteQuery()

}

CreateSubSite “Ragnarok”

CreateSubSite “Anotherrok”

Breadcrumb for Office 365 (SharePoint Online)

One of the user-friendly + familiar navigation elements within SharePoint is the ‘breadcrumb’.   This has been removed from the standard SP2013 and O365 display – but is fairly easy to add back.

The simple tips, from some Googling, is to update the MasterPage – and remove the ‘display:none’ settings.

BUT – I don’t want to have make changes to my MASTER PAGE.

The other consideration is that people (users) don’t really need to jump back up the chain of breadcrumbs, but only need to “go up”.

ParentWeb via REST

My solution was to use some jQuery – and call the REST endpoint to get the parent web – and then poke in some HTML for this HREF.

image

Once you have jQuery in place (via JS Injection), then just refer to a JS file with the following code.    

Lastly, find a nice UP icon from an images search, or icon library, or use the one below – scroll to the bottom – and add a comment, while you’re there…    Smile

image

This handles the TOP site also – and doesn’t add the UP icon – as it’s already the top site.    And there is a hover text showing “back up to > xxxxxx” from the parent web title.

Thanks to this blogpost from Shantha Kumar T, for the REST + Parent Web idea…

Here’s the code – if you’d like to copy/paste – let me know if you find this useful !

$.ajax(

{

    url: _spPageContextInfo.webAbsoluteUrl + “/_api/web/parentweb”,

    method: “GET”,

    headers: {“Accept”: “application/json; odata=verbose”},

    success: function(data)

    {

if (data.d.ServerRelativeUrl)

        {

            console.log(‘Parent site title: ‘ + data.d.Title)

var upSiteUrl = “”;

            upSiteUrl += “<span class=’upLink’>”

            upSiteUrl += “<a href='” + data.d.ServerRelativeUrl + “‘ title=’Back up to > ” + data.d.Title + “‘>”

            upSiteUrl += “<img src=’/sites/global/SiteAssets/IMG/up16.png’>”

            upSiteUrl += “</a>”

            upSiteUrl += “</span>”;

            $(‘#zz11_RootAspMenu’).before(upSiteUrl);

        }

    }

});

Icons – originally as a 128×128 – I resized to 16×16 – and changed the colour using PAINT.NET.

up16up

Problems with 4K UHD and HyperV VM

As part of my new ‘kachirho’ company setup, I’ve purchased a new laptop – DELL XPS15 9550…    It’s a beast with new SkyLake i7 6700 processor – and 16GB RAM – and 512 SSD – see here for a YouTube review.   And it looks super-sexy with carbon finish…

It’s got the new (almost 4K) UHD resolution – 3840×2160 – and touch screen.   The visuals on this resolution are amazing – BUT – the clincher is that you need to ‘up’ the font size scaling.

By default, it was set to 200% – if setting to 100% – it’s hard to read !

The next dilemma – and discussion with a colleague – was the scenario for a HYPER-V VM.    I have one for some local development and working with K2.

Yes, I could use an Azure VM – but I have a local VM – so there…

While I can run a VM at decent speed, and with 10GB RAM, it’s not something that I can run on ‘multiple monitors’.  

The next consideration, was to drop my screen resolution to 1920×1080 – to match the second external monitor.    This WORKED – to a degree.

The VM would now pick up the base screen resolution (3840×1920) – within the VM – regardless of the desktop resolution I’d just changed to.

It turns out that HyperV must grab the base screen resolution – at BOOT time.

So – the answer was :

  • Change down from scale at 200% to 100% (I use 175% a lot of the time)
  • Drop the laptop screen resolution to 1920×1080
  • Reboot
  • Start up the VM
  • Connect – and tick the box for ‘span multiple monitors’

I can now run my VM across both screens – and both at 1920×1080.

It’s a fiddly way to do it – but luckily it’s so quick to re-boot, it doesn’t matter !

Yes – as Scott Hanselman mentioned, living a High-DPI desktop lifestyle can be painful….

#FirstWorldProblems

But – it’s a problem I’m happy to live with.    Viva la 4K !!

Smile

JavaScript Date Format

One of my main bugbears with JavaScript is the way it handles DATE variables.   Or, more specifically, date FORMAT.

There are some great jQuery plug-ins – like MOMENT.JS – but if you have the need for a quick formatter – I just use a simple function, like this :

function formatDate(dateObject) {
    var d = new Date(dateObject);
    var day = d.getDate();
    var month = d.getMonth() + 1;
    var year = d.getFullYear();
    if (day < 10) {
        day = “0” + day;
    }
    if (month < 10) {
        month = “0” + month;
    }
    var date = day + “/” + month + “/” + year;
    return date;
}

This does a format of “dd/mm/yyyy” – which is the only one you need, right ??

To use the above function, you just need to do this :

var nowDateTime = new Date();
var formattedDateTime = formatDate(nowDateTime);

Feel free to edit/use as you need – can change to dd-mm-yyyy, for example.

But never mm/dd/yyyy – that’s just crazy talk !   

😛

Remove all webparts

For a recent SP2010 to SP2013 upgrade, we have a SharePoint Publishing page that has some SandBoxed Solution webparts.   We need to REMOVE these webparts – as we’re going with a different approach – some JavaScript/CSOM – to do the same function.

Anyway, I’ve defined a script that will get a SP page, and remove ALL webparts.

This is via PowerShell + CSOM, so the same approach would would work for SP2013, or O365 (just need a different ClientContext – and login/connection).

# values from constants
$urlSite = "https://intranet/projectX"

$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($urlSite)
$ctxWeb = $ctx.Web
$ctx.Load($ctxWeb)
$ctx.Load($ctxWeb.Lists)
$ctx.ExecuteQuery();
	
[Microsoft.SharePoint.Client.Publishing.PublishingWeb] $pubWeb = [Microsoft.SharePoint.Client.Publishing.PublishingWeb]::GetPublishingWeb($ctx, $ctxWeb);
$ctx.Load($pubWeb)
$ctx.ExecuteQuery();

function RemoveAllWebParts($pageName)
{
    $messageText = "Checking page " + $pageName
	$messageText 

	# remove webparts 
	# wipe them out - ALL of them      (order 66)

	# get the pages library		
	$pagesLib = $ctxWeb.Lists.GetByTitle("Pages");
    $ctx.Load($pagesLib)
	$ctx.ExecuteQuery();
		
	# find the page we wanna ditch the webparts from
    [Microsoft.SharePoint.Client.CamlQuery] $camlQuery = New-Object Microsoft.SharePoint.Client.CamlQuery
	$camlQuery.ViewXml = "<View Scope='RecursiveAll'><Query><Where><Eq><FieldRef Name='FileLeafRef' /><Value Type='Text'>" + $pageName + "</Value></Eq></Where></Query><RowLimit>1</RowLimit></Query></View>"
	$pageFound = $false
	$pageItems = $pagesLib.GetItems($camlQuery)
	$ctx.Load($pageItems)
	$ctx.ExecuteQuery();

	if($pageItems.Count -gt 0)
	{	$pageFound = $true
		$pubPageItem = $pageItems[0]
		$pubFile = $pubPageItem.File
	}
		
	if($pubFile.CheckOutType -eq [Microsoft.SharePoint.Client.CheckOutType]::None -or $pubFile.CheckOutType -eq $null)
	{
		try
		{
			$pubFile.CheckOut()
			$ctx.Load($pubFile)
			$ctx.ExecuteQuery(); 
		}
		catch
		{
		}
	}

	$wpm = $pubFile.GetLimitedWebPartManager([Microsoft.SharePoint.Client.WebParts.PersonalizationScope]::Shared);
    $webparts = @();

	$wpcoll = $wpm.WebParts;
	$ctx.Load($wpcoll);
	$ctx.ExecuteQuery();

    $messageText = "Delete webparts for the page";
    $messageText;

    foreach($spwebpart in $wpcoll)
    {
        $webparts = $webparts + $spwebpart.ID;
        $spwebpart.deleteWebPart();
        $ctx.ExecuteQuery();
    }

	if($pubFile.CheckOutType -ne [Microsoft.SharePoint.Client.CheckOutType]::None)
	{
		$pubFile.CheckIn("", [Microsoft.SharePoint.Client.CheckinType]::MajorCheckIn);
		$pubFile.Publish("");
		$pubFile.Approve("");
	}
}

RemoveAllWebParts 'Page1.aspx';
RemoveAllWebParts 'Page2.aspx';
RemoveAllWebParts 'Page3.aspx';
RemoveAllWebParts 'Page4.aspx';

O365 Saturday Melbourne

Last weekend, I presented a session at the Office 365 Saturday held in Melbourne.   I didn’t get there until lunch was being served, as I was coaching my son’s basketball team – but there was a great buzz & lots of chatter when I got there !

My session was entitled “VS, PS, CS, JS and no BS”.

If you don’t know the acronyms, it was a session about :

  • VS = Visual Studio 2015
  • PS = PowerShell
  • CS = C#
  • JS = JavaScript
  • BS = well, there was none, right !?    

🙂

Using Visual Studio 2015 as my IDE, I covered three main areas, with corresponding demos.

Provision a Site Collection – using PowerShell

  • Connect to SPO
  • Create a new Site Collection
  • Regional Settings
  • Activate features
  • Upload files
  • Create lists & pages
  • Set Alt Css Url
  • Welcome page
  • Add JavaScript reference (JS injection)

This last point was a topic that I’ll cover in detail in another post – a way to get some good functionality into your SharePoint O365 site, without needing to change the Master Page – otherwise known by some work colleagues as ‘dentistry via the belly button’ – LOL !

Provider hosted app in Azure – using C#

Using CSOM, users can provision a sub-site (SPWeb), and have it configured with a variety of features and layout.   My code even provisions a Yammer group if required.

  • Activate features
  • Add lists
  • Create webparts
  • Set security
  • Create Yammer group
  • Add Yammer webpart to page
  • Add blog site
  • Add audience targetted content editor webparts

Additional functionality – using JavaScript

Lastly, I showed some of the functions we developed using CSOM and the JSOM :

  • Add footer – without Master Page update (JavaScript injection)
  • Waffle
  • Show dialog
  • Delete sub-sites
  • Get AppTiles

Much of the demo’s were fairly fast paced, and “you had to be there to see it” – but I’ve loaded the slides for your viewing – and will blog some of the items above in depth.

Let me know if you would like to see/hear more – and thanks for attending my session – if you were there on the day.    (no BS, was there ??   right !!?!?)

Create Yammer group as another user

We have a use-case in which we’re provisioning Office365 site collections, on behalf of a user request (via a SQL database – don’t worry about that bit, it’s kinda inconsequential).

We’re using some Azure C# website/webjob magic to put it all together.

BUT – the problem we were facing was this :

  • Cannot create a Yammer group ‘as another user’
  • Cannot add a user TO a group
  • Cannot add an admin to a group

We could create a group as an “farm-admin” user, but when creating a private group, the requesting user couldn’t access it – and needed the god-mode user to grant access !!

So much for a self-service site creation + Yammer – which was our goal.

Long post ahead, but the short story is that you need to ‘impersonate’ another user, when you attempt to create a new Yammer group  :

  • Call a Yammer REST endpoint to get all users
  • Loop through the users to find the requesting user (eg. Jodie)
  • Call a Yammer REST endpoint to get the user by email address – rather than get ALL users
  • Call a Yammer REST endpoint to get the security token for that user
  • Call a Yammer REST endpoint to create a group – passing the user’s access token

This does EXACTLY what we need it to do – and creates a group ‘as the user’ – and as a by-product, the user is made an admin – perfect !

A fair chunk of this approach was sourced from a blog post from Paul Matthews (cannonfodder) who helped me on the right track…

And also Steve Peschka (samlman)’s blogpost about Using Impersonation with the Yammer APIs.

Here’s the details of what *I* implemented – and the outcome achieved…….

Register a Yammer App – get client Id/Secret/Token

Before you begin, you need to get a client id / secret – much like a SharePoint app.

Refer to my previous post which covers this – and describes the group creation – which was our initial approach.

NB.  I have a helper method to GET/POST to Yammer :

private static string CallYammerJson(string url, string accessToken, string method)
{
    //make the request
    string json = null;
    HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
    request.Method = method;
    request.Headers.Add('Authorization', 'Bearer' + ' ' + accessToken);
    using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
    {
        Encoding encode = Encoding.GetEncoding('utf-8');
        StreamReader reader = new StreamReader(response.GetResponseStream(), encode);
        json = reader.ReadToEnd();
    }
    return json;
}

This method can be used when you craft a URL – and just pass a token, and a GET or POST as the method.

Get Yammer Users

This code then retrieves a set of the users within Yammer – as a JSON structure.   I’m using a YammerUser class – you can grab this from the PnP example.

string yammerUserAccountEmail = 'i:0#.f|membership|jodie@whatever.com.au';
int yamUserId = 0;

//get all users - and find the yammer user id
string url = 'https://www.yammer.com/api/v1/users.json';
var response = CallYammerJson(url, YAMMER_API_TOKEN, 'GET');
List[YammerUser] users = JsonUtility.Deserialize[List[YammerUser]](response);

foreach (YammerUser u in users)
{
    string yamEmail = u.contact.email_addresses[0].address.ToLower();

    if (yammerUserAccountEmail.ToLower().Contains(yamEmail) )
    {
        yamUserId = u.id;
        break;
    }
}

UPDATE > Change to [users/by_email.json]

We’ve since discovered that you can get the user details ‘by email address’ – rather than needing to get *ALL* users, which could be many thousands…    (a big concern)

Instead, we can call the REST endpoint with an email address parameter.  This gets us the ONE user, in the same JSON chunk.    This is much MUCH more elegant.

//yammerUserAccountEmail  >>  i:0#.f|membership|999999@domain.com.au
string[] emailArray = yammerUserAccountEmail.Split('|');

//0 > i:0#.f
//1 > membership
//2 > 999999@domain.com.au
string emailO365 = emailArray[2];

const string YAMMER_USERS_BY_EMAIL_URL = 'https://www.yammer.com/api/v1/users/by_email.json?email={0}';
string url = string.Format(YAMMER_USERS_BY_EMAIL_URL, emailO365);

//get the user - and find the yammer user id
var response = GetYammerJson(url, YAMMER_API_TOKEN);
List[YammerUser] users = JsonUtility.Deserialize[List[YammerUser]](response);

//found the users collection - will only have ONE user 
if (users.Count > 0)
{
    //get the token and return it
    YammerUser u = users[0];
    yamUserId = u.id;
}

Get user auth token

After you have an Yammer “ID” of the person (eg. Jodie) – you can then call another REST endpoint, to determine their access token.

You need to include the CLIENT ID in the url – from when you registered your App – as well as the ID of the user.   The token is the same ‘admin’ token (at this stage).

//get the user token
string userToken = string.Empty;
string tokenUrl = 'https://www.yammer.com/api/v1/oauth/tokens.json?consumer_key={0}&user_id={1}';
string url = string.Format(tokenUrl, YAMMER_CLIENT_ID, yammerUserId.ToString());

var response = GetYammerJson(url, YAMMER_API_TOKEN);
List[YammerUser] users = JsonUtility.Deserialize[List[YammerUser]](response);

if (users.Count != 0)
{
    YammerUser u = users[0];
    userToken = u.token;
}

Create group, impersonating another user

bool privateYammerGroup = true;    // or false - if you want a public group
string privateYammerGroup = 'Hunting and Fishing';

string url = 'https://www.yammer.com/api/v1/groups.json?name={0}&private={1}';
string urlCreateGroup = String.Format(url, yammerGroupName, privateYammerGroup.ToString().ToLower());

string jsonCreateGroup = CallYammerJson(urlCreateGroup, userToken, 'POST');

That’s about it !

It’s not actually TOO bad – it took us a lot longer to determine the approach, than to actual do it.

We’d initially tried to create a group, and then assign an admin user – after watching the HTTP traffic via Fiddler, and some emails with the product team, it was clear that we were barking up the wrong tree.

The above solution works nicely – we’re now rolling this into a production tenant with Azure/O365.

Users can simply enter the name of a ‘site’ and get a new site collection, and yammer group also – nice !

🙂

UPDATE : Missing Code :

Sorry – had skipped this function – I used it for GET and POST – just by passing the word as ‘method’.

Eg.  PostYammerJson(“http://url&#8221;, “lskdjshdfkjhs”, “GET”)

==================

private static string PostYammerJson(string url, string accessToken, string method)
{
//make the request
string json = null;
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.Method = method;
request.Headers.Add(“Authorization”, “Bearer” + ” ” + accessToken);

try
{
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
Encoding encode = Encoding.GetEncoding(“utf-8”);
StreamReader reader = new StreamReader(response.GetResponseStream(), encode);
json = reader.ReadToEnd();
}
}
catch(Exception ex)
{
json = ex.Message;
}

return json;
}