Sunday, 20 December 2015

SharePoint: How do I get all the groups for the current user in Javascript and AngularJS?

SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function () {

$scope.UserGroups = [];

var clientContext = new SP.ClientContext.get_current();
this.collGroup = clientContext.get_web().get_siteGroups();
currentUser = clientContext.get_web().get_currentUser();
clientContext.load(collGroup);
clientContext.load(collGroup, 'Include(Users)');
clientContext.load(currentUser);

clientContext.executeQueryAsync(Function.createDelegate(this, onQuerySucceeded), Function.createDelegate(this, onQueryFailed));

function onQuerySucceeded() {
var groupEnumerator = collGroup.getEnumerator();
while (groupEnumerator.moveNext()) {
var oGroup = groupEnumerator.get_current();
var collUser = oGroup.get_users();
var userEnumerator = collUser.getEnumerator();
while (userEnumerator.moveNext()) {
var oUser = userEnumerator.get_current();
if (oUser.get_loginName() == currentUser.get_loginName())
{
$scope.UserGroups.push({
"ID": oGroup.get_id(),
"Name": oGroup.get_title()
});
}
}
}
}

function onQueryFailed(sender, args) {

alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
});

Sunday, 6 December 2015

AngularJS: Why does my module not render in my webpage?

I have been coding a simple Single Page Application solution using Angular JS running a in content editor web part in SharePoint 2010.

I had the following code in my .aspx page:

<div class="table-cell mainbody">
<div>
<div id="first" ng-controller="Controller1" ng-include="src='/StyleLibrary/pages/first.Module.html'" ></div>
<div id="second" ng-controller="Controller2" ng-include="src='/StyleLibrary/pages/second.Module.html'" ></div>
</div>
</div>


However, when I ran $("#second") in my console window, I received an empty object.

Hmmmmmm.

It appears that that each module injection needs to be surrounded by its own DIV, so I change the code to the following to make it work:

<div class="table-cell mainbody"> 
<div>
<div id="first" ng-controller="Controller1" ng-include="src='/StyleLibrary/pages/first.Module.html'" ></div>
</div>
<div>
<div id="second" ng-controller="Controller2" ng-include="src='/StyleLibrary/pages/second.Module.html'" ></div>
</div>
</div>

Wednesday, 28 October 2015

SharePoint: How do I get the location of the MySite url using PowerShell?

[void][reflection.assembly]::Loadwithpartialname("Microsoft.Office.Server");
[void][reflection.assembly]::Loadwithpartialname("Microsoft.Office.Server.UserProfiles");
[void][reflection.assembly]::Loadwithpartialname("System.Web");
[void][reflection.assembly]::Loadwithpartialname("Microsoft.SharePoint");

$mySiteUrl = "http://mysitecollection.com"
$sc = Get-SPServiceContext($mySiteUrl)
$upm = new-object Microsoft.Office.Server.UserProfiles.UserProfileManager($sc)
Write-Host $upm.MySiteHostUrl

NOTE: If you encounter a 'Permission Denied' error, make sure that your user has 'Full Control' permissions on the User Profile Service.

Monday, 26 October 2015

SharePoint: How do I upload a file structure to a document library?

My current project uses a custom 'style library' document library. The deployment process needed to replicate the files and file structure from the disk in the target library.

I used CSOM and Powershell to solve the problem.

I created a 'Common' dll that contains the methods I needed:
1. CreateFolder (to create a folder structure)
2. UploadFile (to load the file).

    public static class FileHelper
    {
        public static Folder CreateFolder(Web web, string listTitle, string fullFolderPath)
        {
            if (string.IsNullOrEmpty(fullFolderPath))
                throw new ArgumentNullException("fullFolderPath");
            var list = web.Lists.GetByTitle(listTitle);
            return CreateFolderInternal(web, list.RootFolder, fullFolderPath);
        }

        private static Folder CreateFolderInternal(Web web, Folder parentFolder, string fullFolderPath)
        {
            var folderUrls = fullFolderPath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            string folderUrl = folderUrls[0];
            var curFolder = parentFolder.Folders.Add(folderUrl);
            web.Context.Load(curFolder);
            web.Context.ExecuteQuery();

            if (folderUrls.Length > 1)
            {
                var folderPath = string.Join("/", folderUrls, 1, folderUrls.Length - 1);
                return CreateFolderInternal(web, curFolder, folderPath);
            }
            return curFolder;
        }

        public static void UploadFile(string siteUrl, string sourceFileFullLocation, string targetListTitle, string targetLocation)
        {
            UploadFile(siteUrl, sourceFileFullLocation, targetListTitle, targetLocation, new Dictionary<string, string>());
        }

        public static void UploadFile(string siteUrl, string sourceFileFullLocation, string targetListTitle, string targetLocation, Dictionary<string, string> items)
        {
            using (var context = new ClientContext(siteUrl))
            {
                ConnectionHelper.SetCredential(context);

                var web = context.Web;
                context.Load(web);
                context.Load(web.CurrentUser);
                context.Load(web.Lists);
                context.ExecuteQuery();

                var newFile = new FileCreationInformation()
                {
                    Overwrite = true,
                    Content = System.IO.File.ReadAllBytes(sourceFileFullLocation),
                    //Url = targetLocation,
                    Url = Path.Combine(targetLocation, Path.GetFileName(sourceFileFullLocation))
                };

                var targetFolder = context.Web.GetFolderByServerRelativeUrl(targetLocation);
                context.Load(targetFolder);
                context.ExecuteQuery();

                Microsoft.SharePoint.Client.File uploadFile = targetFolder.Files.Add(newFile);

                if (items != null)
                {
                    foreach (KeyValuePair<string, string> item in items)
                    {
                        uploadFile.ListItemAllFields[item.Key] = item.Value;
                    }
                }

                uploadFile.ListItemAllFields.Update();

                context.Load(uploadFile);
                context.ExecuteQuery();
            }
        }

Once the code was compiled into a dll, I instantiated them in my powershell code:

Function LoadDll($location)
{
$bytesCommon = [System.IO.File]::ReadAllBytes("$location")
$loadResultCommon = [System.Reflection.Assembly]::Load($bytesCommon)
}

The last part is to recursively loop through the folder structure and create the items in the target location

Function UploadFiles($siteUrl, $sourceFolder, $siteRelativeUrl)
{
$listName = "MyStyleLibraryList"
CreateLibrary $siteUrl $listName

Get-ChildItem $sourceFolder -Filter *.* -Recurse | 
Foreach-Object{
$name = $_.Name
$folder = $_.FullName.Replace($sourceFolder, "").Replace("\", "/")
$folder = $folder.Substring(1, $folder.Length - 1)
if ($_.PSIsContainer -eq $true)
{
Write-Host "Creating folder $folder"
CreateFolder $siteUrl $listName $folder
}
else
$folderWithFileName = $_.FullName.Replace("$sourceFolder", "")
$folder = $folder.Replace($_.Name, "")
UploadFile $siteUrl  $_.FullName $listName "$listName/$folder" $folderWithFileName
}
}
}

Function CreateFolder($siteUrl, $listName, $folderName)
{
[MyHelperSolution.FileHelper]::CreateFolder($siteUrl, $listName, $folderName)
}

Function UploadFile($siteUrl, $sourceFolder, $listName, $siteRelativeUrl, $folderWithFile)
{
[MyHelperSoltuion.FileHelper]::UploadFile($siteUrl, $sourceFolder, $listName, $siteRelativeUrl)
}

Sunday, 4 October 2015

JQuery DatePicker: How do I add a watermark?

A watermark is always a great way to provider a meaningful hint to a user. Using the JQuery DatePicker control, I wanted to tell the user the expected format of the date.

 I added a new class called 'watermarkrequired' to the required html elements to help me identify them.

I looked at several option:
1. Using the blur and focus events.

$('.watermarkrequired').blur(function () {
if ($(this).val().length == 0)
   $(this).val(watermark).addClass('watermark');
}).focus(function () {
if ($(this).val() == watermark)
    $(this).val('').removeClass('watermark');
}).val(watermark).addClass('watermark');

2. Using the 'AppendText' option of the DatePicker

$( ".watermarkrequired" ).datepicker( "option", "appendText", "(yyyy-mm-dd)" );

Finally, I settled on the placeholder attribute

$( ".watermarkrequired" ).attr("placeholder", "dd/mm/yyyy");

Monday, 28 September 2015

FAST Search: Failed to communicate with the WCF service

I was creating my managed properties to my FAST server (via FAST PowerShell) when I encountered this error

Get-FASTSearchMetadataManagedProperty : Failed to communicate with the WCF service

The resolution was pretty simple - I followed the steps in this link:
1. Add the user to the Windows group FASTSearchKeywordAdministrators
2. Log off the machine and log in again.
3. Problem resolved.

Tuesday, 11 August 2015

JQuery UI Dialog: Cannot call methods on dialog prior to initialization

My current project (using Angular JS on SharePoint 2010) requires a modal dialog window with some data passed between the different modules. We used dialog-service from GitHub, which is a great utility for this purpose.

All was great until I deployed the solution to a new environment. Suddently, my modal dialog stopped working and was complaining about 'initialization' issues.

I stepped through the code and found that the initialization code was being called and dialog.ref.is(':data(dialog)') was returning true. So what was different?

To cut a long story short, the culprit was the Document ID Service (which was enabled on the new environment). It was appending text to the HTML, which was causing all the problems.

Two solutions presented themselves:
1. Disable the feature (not likely) or
2. trim the appended text:

if (html.indexOf('<html xmlns:') >= 0)
   html = html.substring(0, html.indexOf('<html xmlns:')){
}

I chose the latter. Not the most elegant solution, but effective.

Thursday, 23 July 2015

Why are my accounts always locking/not authenticating when connecting to an old site in Windows 7?

I recently changed my password and found that all of my old connections were failing. And I was not being prompted to enter new credentials.

Fortunately, the fix is pretty simple. Windows 7 has a Credential Manager (in the Control Panel) that stores all the saved information. I updated the relevant passwords and all was well with the world.

Sunday, 19 July 2015

SharePoint 2010: How do I set the value multi select lookup column?

A seed data script I was writing required a multi value selection lookup column to be pre populated. Setting a single value is simple - use <Id>;#<Text> - and all is well. But how do you set multiple values?

The solution is just as simple; use ;# as the delimiter between items. For example, you could set your column with the following:

<ID1>;#<Text1>;#<ID2>;#<Text2>

Semi-colon Hash (;#) is the delimiter of choice.

Tuesday, 26 May 2015

SharePoint 2010: How do I install SharePoint Server on Windows 7?

Once you have download SharePointServer.exe, you will need to extract the files from the executable.

From the command prompt run: SharePointServer.exe /extract:c:\sp2010\

This will extract the installation files.

Second, you need to 'enable' a Windows 7 installation. In order to do this, you will need to edit one of the configuration files. Navigate to C:\sw\sp2010\Files\Setup and add the following line to the bottom on the config.xml (before the closing Configuration tag).

<Setting Id="AllowWindowsClientInstall" Value="True"/>

You should be good to go IF YOU HAVE SQL SERVER 2008 R2. If you are running SQL Server 2012, you will need SharePoint Server 2010 SP1.

Tuesday, 12 May 2015

MVC 4: The required anti-forgery form field "__RequestVerificationToken" is not present

It seems the best things in life come in pairs:

If you have a ValidateAntiForgeryToken annotation in your controller, you need to have a @Html.AntiForgeryToken() in your view.


Tuesday, 5 May 2015

Windows Server 2012: Why cant I install Windows Server 2012 RTM on a Virtual Box?

I encountered this problem today while setting up a SharePoint 2013 Development environment.

The solution is to run a simple command:

"C:\Program Files\Oracle\VirtualBox\VBoxManage" setextradata [vmname] VBoxInternal/CPUM/CMPXCHG16B 1

For example, my Virtual Box was called SP2013, so my command was:

"C:\Program Files\Oracle\VirtualBox\VBoxManage" setextradata SP2013 VBoxInternal/CPUM/CMPXCHG16B 1

Sunday, 3 May 2015

Why cant I add a new web application is my Windows 7 Sharepoint environment?

I recently installed a test environment on my Windows 7 laptop [using New-SPConfigurationDatabase] but I was unable to add a new Web Application. I created a new user, added it to the Local Administrators group.

Two issues were apparent:
A. I could not add a new Farm Administrator as 'I needed Local Administrator rights' [from the ULS]
B. I could not create a new Web Application [due to security trimming]

The solution lies in the OOTB Administrator account that is provisioned in the Windows 7 installation; it is deactivated by default.

I activated the account and, presto, used its credentials to perform all the required activities.

Wednesday, 15 April 2015

Why cant I add a SharePoint project to Visual Studio 2013 Community Edition?

While configured my AWS development machine for SharePoint, I found that my 'SharePoint' options were missing when I tried to create a new project.

Fortunately, all I had to do was download the Office Tools for Visual Studio 2013 from here

Monday, 13 April 2015

SharePoint 2013 Pre requisites install fail, Error: The tool was unable to install Application Server Role, Web Server (IIS) Role.

I was configuring a new development environment in Amazon Web Services and I encountered the following error message:




SharePoint 2013 Pre requisites install fail, Error: The tool was unable to install Application Server Role, Web Server (IIS) Role.




I tried several suggestions from Google, but to no avail. The problem was I was trying to solve an unsolvable problem. Huh? I was attempting to install SharePoint Foundation 2013 on Windows Server 2012 R2 WHICH IS NOT SUPPORTED.




I needed to install SharePoint Foundation 2013 SP1.

Tuesday, 24 March 2015

How can I easily debug an email utility in SharePoint?

Working with emails (especially in Timer Service jobs) can be a pain to debug. Fortunately, there are a few freely available utilities that make this chore very simple.

I recently wrote a simple server that send numerous emails using the following code:

                        var headers = new StringDictionary
                        {
                            {"from", web.Site.WebApplication.OutboundMailSenderAddress},
                            {"to", user.Email},
                            {"subject", subject},
                            {"content-type", "text/html"}
                        };

                        SPUtility.SendEmail(web, headers, CreateEmailMessage());

I then downloaded the fantastic FakeSMTP to act as my SMTP server. This gem intercepts emails on the default mail port (25). Download the code and run 'java -jar fakeSTMP.jar'. (Be sure to start the server before you begin).

The next step is to configure SharePoint to use the local machine as its SMTP server.
In Central Administration -> System Settings, select 'Configure outgoing email settings'


Set the 'Outbound SMTP server' to the full name of your development machine. You can get the name from 'Control Panel\All Control Panel Items\System'

You are now in a position to retrieve the emails. The last piece of the puzzle is to download an eml viewer. I use FreeViewer.

NOTE: Remember to associate .eml files to this application or you will be prompted when double clicking on the email.

Monday, 9 March 2015

SharePoint 2013: What do the items in a claims token mean?

The code for 'All Authenticated Users' is c:0(.s|true.

Its all very confusing - but everything is revealed here

SharePoint 2013: How can I get a users SPPrincipal token?

In a SharePoint Claims authenticated environment, you need to extract the full claims token (not just DOMAIN\Username). Here are two simple methods using the ResolvePrincipal function to get the information:

PowerShell:

function GetUserPrincipalFromUsername($siteUrl, $login)
{
$web = Get-SPWeb $siteUrl
$principal = [Microsoft.SharePoint.Utilities.SPUtility]::ResolvePrincipal($web, $login, [Microsoft.SharePoint.Utilities.SPPrincipalType]::All, [Microsoft.SharePoint.Utilities.SPPrincipalSource]::All, $null, $false)
$web.Dispose()
return $principal
}

C#:

public SPPrincipalInfo GetUserPrincipalFromUsername(SPWeb web, string userName)
{
  return SPUtility.ResolvePrincipal(web, login, SPPrincipalType.All, SPPrincipalSource.All, null, false);
}

Thursday, 5 March 2015

SharePoint 2013: Error updating managed account credentials

I encountered the following message when trying to change a password for a managed account:

Error deploying administration application pool credentials. Another deployment may be active. An object of the type Microsoft.SharePoint.Administration.SPAdminAppPoolCredentialDeploymentJobDefinition named "job-admin-apppool-change" already exists under the parent Microsoft.SharePoint.Administration.SPTimerService named "SPTimerV4".  Rename your object or delete the existing object.

The error message is self explanatory - Rename your object or delete the existing object. I choose the latter. Powershell to the rescue.

$job = Get-SPTimerJob -Identity "job-admin-apppool-change"
$job.Delete()

Reset the password and all should be good.

Wednesday, 4 March 2015

SharePoint 2013: The sandboxed code execution request was refused because the Sandboxed Code Host Service was too busy to handle the request.

This was a nasty problem to resolve. I googled it and went through some basic steps:

1. follow the suggestions from msdn blog (here)
2. restart the windows service
but no luck.

I then restarted the SharePoint Service (Central Administration -> Settings -> Services on Server) and found that the password for my managed account had expired.

I ran the powershell script Set-SPManagedAccount to correct the problem.

SharePoint: The contents of the feature’s solution requires the Solution Sandbox service to be running

In 'System Settings' -> 'Manage Services on Server', ensure that the service 'Microsoft SharePoint Foundation Sandboxed Code Service' is running.

Open a new instance of PowerShell and you are good to go ...

Thursday, 26 February 2015

ShaerPoint 2013: How do I change the picture (source) for an image at runtime?

An image can easily be converted into a 'clickable' image with some simple html manipulation - put the image in an anchor tag.

I have added two images (First.ico and Second.ico) to the Style Library/images of my site collection. I also have jQuery referenced in my masterpage.

Here is the html:

<a href="" onclick="changeImage()"><img id="favourite" ></img></a>

Changing the image is just as easy:

First, lets set an initial source value:

jQuery(window).load(function () {
   $("#favourite").attr("src", "/Style%20Library/Images/First.ico");
})

Then, implement the changeImage function to switch images:

function changeImage() {
if ($("#favourite").attr("src") == "/Style%20Library/Images/First.ico")
{
$("#favourite").attr("src", "/Style%20Library/Images/Second.ico");
}
else
{
$("#favourite").attr("src", "/Style%20Library/Images/First.ico");
}
}

Wednesday, 25 February 2015

SharePoint 2013: How do I reference a html 'source' file from another site collection in a Content Editor Web Part?

Content Editor Web Parts are fantastic for rendering content, but there is small problem if you want to reference a file (through a ContentLink) on another site collection - you are not allowed to! Cross-site scripting is not a good thing.

This problem becomes apparent when you have a 'source' site collection that will host all the html/js files and you want all other site collections to reference this (single) source of truth.

There are a few options available to resolve the problem.
1. Copy all the JS/Html files to each site collection and reference them locally. (Easy, but it will create a maintenance nightmare if you have lots of site collections).
2. Enable Anonymous access to the 'source' site collection. (Easy, but not a great solution)
3. Install Content Link Web Part from Codeplex. (Better, but requires a Farm Solution)
4. Move the link from the ContentLink to the Content in the webpart.
The key to resolving the problem is nested CDATA tags.

In my project, we were using AngularJS, so used ng-include to point to my source file.

Here is my sample from the elements.xml for my page Module:

<AllUsersWebPart WebPartOrder="0" WebPartZoneID="RowLeft">
<![CDATA[
<WebPart xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/WebPart/v2">
  <Title />
  <FrameType>None</FrameType>
  <Description></Description>
  <IsIncluded>true</IsIncluded>
  <ZoneID>RightRow</ZoneID>
  <PartOrder>0</PartOrder>
  <FrameState>Normal</FrameState>
  <Height />
  <Width />
  <AllowRemove>false</AllowRemove>
  <AllowZoneChange>false</AllowZoneChange>
  <AllowMinimize>false</AllowMinimize>
  <AllowConnect>false</AllowConnect>
  <AllowEdit>false</AllowEdit>
  <AllowHide>false</AllowHide>
  <IsVisible>true</IsVisible>
  <DetailLink />
  <HelpLink />
  <HelpMode>Modeless</HelpMode>
  <Dir>Default</Dir>
  <PartImageSmall />
  <MissingAssembly>Cannot import this Web Part.</MissingAssembly>
  <PartImageLarge>/_layouts/15/images/mscontl.gif</PartImageLarge>
  <IsIncludedFilter />
  <Assembly>Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
  <TypeName>Microsoft.SharePoint.WebPartPages.ContentEditorWebPart</TypeName>
  <ContentLink xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor" />
  <Content xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor"><![CDATA[
<div id="description" ng-include="src='/Style Library/html/MyModule.html'" ></div>
  ]]]]><![CDATA[>
  </Content>
  <PartStorage xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor" />
</WebPart>
]]>
</AllUsersWebPart>

The key with nested CDATA is to close the root tags immediately once the nested tags are used and create a new CDATA tag immediately afterwards.


Wednesday, 11 February 2015

Sharepoint 2013: Why is my deployed dll not in the GAC?

I have created a new SharePoint wsp and it has been successfully deployed, but when I searched for the compiled component in the GAC, it was nowhere to be seen. Huh?

The simple solution is that the GAC is .net 1.0 - 3.5 is not the same as the GAC in .net 4.0+.

The old GAC is the trusted location c:\windows\assembly
The new GAC resides in c:\windows\microsoft.net\assembly

It was there after all - it helps when you look for the file in the right place.

Wednesday, 4 February 2015

PowerShell: How do I open a reference without locking it?

I was recently writing a C# dll to the invoked in PowerShell. As I tested the PowerShell script, I found that the dll was being locked and any re-compiles from Visual Studio were being rejected.

I used the following code to resolve the problem:

$bytesCommon = [System.IO.File]::ReadAllBytes("c:\folder\driver.dll")
$loadResultCommon = [System.Reflection.Assembly]::Load($bytesCommon)

Monday, 2 February 2015

PowerShell: How do I append data to an existing file in SharePoint with a new line and quotes?

A recent proejct required the injection of some runtime data in my require js configuration file. The following code allowed me to inject the new values into the file.

$site = Get-SPSite "http://mysite"

$file = $site.RootWeb.GetFile("/Style Library/myfile.js")

if (($file -ne $null) -and ($file.Exists -eq $true))
{
$binaryAsIs = $file.OpenBinary()

$asciiEncoding = New-Object -TypeName System.Text.UTF8Encoding
$asIs = $asciiEncoding.GetString($binaryAsIs)

        # This is the text to append to the top.
        # `r`n will create a crlf (new line)
        # $([char]34) will embed a double quote in the text

$new = "// Here is some next text `r`n //and here is text in $([char]34)quotes([char]34)`r`n"
$newFile = $new + $asIs

$binaryToBe = $asciiEncoding.GetBytes($newFile)

$file.CheckOut()
$file.SaveBinary($binaryToBe)
$file.CheckIn("")
$file.Publish("")
}

$site.Dispose()

Wednesday, 14 January 2015

Powershell: Recusively Check In and Publish files in a SharePoint Document Library

I have combined the scripts from Paul King (here) and Brijendra Gautam (here) to create my own version of the script.

Function CheckInAndPublishFolderItemsRecusively( [Microsoft.SharePoint.SPFolder] $folder )
{
    # Create query object
    $query = New-Object Microsoft.SharePoint.SPQuery
    $query.Folder = $folder

    # Get SPWeb object
    $web = $folder.ParentWeb

    # Get SPList
    $list = $web.Lists[$folder.ParentListId]

    # Get a collection of items in the specified $folder
    $itemCollection = $list.GetItems($query)

    # Iterate through each item in the $folder
    foreach ($item in $itemCollection)
    {
        # If the item is a folder
        if ($item.Folder -ne $null)
        {
            # Call the Get-Items function recursively for the found sub-solder
            CheckInAndPublishFolderItemsRecusively $item.Folder
        }
        else
        {
if ($item.File.Level -ne "Published")
{
           if ($item.File.CheckOutType -ne "None")
           {
               if ($item.File.Versions.Count -eq 0)
               {
                   # Check in the file
                    $item.File.CheckIn("Checked in By Administrator", [Microsoft.SharePoint.SPCheckinType]::MajorCheckIn)
$item.File.Publish('Published using Powershell')
}
                }
}
        }
    }

    $web.dispose()

    $web = $null
}

The script is invoked as follows:
$web = Get-SPWeb $siteUrl
$folder = $web.GetFolder("Style Library")
CheckInAndPublishFolderItemsRecusively $folder

Tuesday, 13 January 2015

SharePoint Online: How do I extract list changes with PowerShell?

Import-Module 'C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell' -DisableNameChecking

$loadInfo1 = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
$loadInfo2 = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")

$webUrl = "https://mysite.sharepoint.com/"
$username = "richard.leeman@mydomain.onmicrosoft.com"
$password = Read-Host -Prompt "Password for $username" -AsSecureString

$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($webUrl)
$ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $password)
$web = $ctx.Web
$webs = $web.Webs
$lists = $web.Lists

$ctx.Load($lists)
$ctx.Load($webs)
$ctx.Load($web)
$ctx.ExecuteQuery()

$list = $lists| Where-Object { $_.Title -eq "My Documents"}

$dt = Get-Date
$dtUTC = $dt.ToUniversalTime()
$changeQuery = New-Object Microsoft.SharePoint.Client.ChangeQuery($true, $true)
$changeQuery.ChangeTokenStart = New-Object Microsoft.SharePoint.Client.ChangeToken
$changeQuery.ChangeTokenStart.StringValue = "1;3;" + $list.Id.ToString() + ";" + $dtUTC.AddHours(-10).Ticks.ToString() + ";-1"
$changeQuery.ChangeTokenEnd = New-Object Microsoft.SharePoint.Client.ChangeToken
$changeQuery.ChangeTokenEnd.StringValue = "1;3;" + $list.Id.ToString() + ";" + $dtUTC.Ticks.ToString() + ";-1"

$changes = $list.GetChanges($changeQuery)
$ctx.Load($changes)
$ctx.ExecuteQuery()

$addedCount = 0
$updatedCount = 0
$deleteCount = 0

if ($changes.AreItemsAvailable -eq $true)
{
$fileName = "sampleData.txt"

$changes > $fileName


$changes | ForEach-Object {
if (($_.ItemId -ne "") -and ($_.ItemId -ne $null))
{
if ($_.ChangeType -eq "Add") { $addedCount = $addedCount + 1 }
if ($_.ChangeType -eq "Update") { $updatedCount = $updatedCount + 1 }
if ($_.ChangeType -eq "DeleteObject") { $deleteCount = $deleteCount + 1 }
}
}

Start-Process notepad  -ArgumentList $fileName
}

Write-Host "$addedCount items added"
Write-Host "$updatedCount items updated"
Write-Host "$deleteCount items deleted"

SharePoint Online: How do I configure the Site Collection Audit Settings from CSOM?

My current project requires that the Site Collection Audit Settings be provisioned with every new OneDrive for Business site. The problem is that there is not existing API (in CSOM or PowerShell) to set these values. Problem? Yes. Insurmountable? No.

I was pointed in the direction of this fantastic blog. The basic idea behind the code is to replicate the 'POST' event that is created by using the UI. The code is magnificent but (as is continually stressed in the video) is not supported by Microsoft. This does not mean that is will not work, it just means that should the name of the underlying fields change, the code will fail unceremoniously.

Anyway, back to the solution. 
Firstly, enable the 'Reporting' Site Collection feature. The feature Guid is "7094bd89-2cfe-490a-8c7e-fbace37b4a34" and should be activated as a FARM level feature

Secondly, here is my add-in code to the set values.

using System.Globalization;

namespace Contoso.Remote.Core.HttpCommands
{
    public class RequestAuditSettings : RemoteOperation
    { 
                #region CONSTRUCTORS

      public RequestAuditSettings(string TargetUrl, AuthenticationType authType, string User, string Password, string Domain = "")
            : base(TargetUrl, authType, User, Password, Domain)
        {
            TrimLogs = false;
            RetentionInDays = 0;
            EditingItems = false;
            CheckinAndCheckout = false;
            MovingAndCopying = false;
            DeletingAndRestoring = false;
            EditContentTypesAndColumns = false;
            SearchSiteContent = false;
            EditUsersAndPermissions = false;
            StorageDocumentLibraryName = "";
        }


        #endregion

        #region PROPERTIES

        public override string OperationPageUrl
        {
            get
            {
                return "/_layouts/15/auditsettings.aspx";
            }
        }

        public bool TrimLogs { get; set; }
        public int RetentionInDays { get; set; }
        public bool EditingItems { get; set; }
        public bool CheckinAndCheckout { get; set; }
        public bool MovingAndCopying { get; set; }
        public bool DeletingAndRestoring { get; set; }
        public bool EditContentTypesAndColumns { get; set; }
        public bool SearchSiteContent { get; set; }
        public bool EditUsersAndPermissions { get; set; }
        public string StorageDocumentLibraryName { get; set; }

        #endregion

        #region METHODS

        public override void SetPostVariables()
        {
            // Set operation specific parameters
            PostParameters.Add("__EVENTTARGET", "ctl00$PlaceHolderMain$BtnOk");
            PostParameters.Add("ctl00$PlaceHolderMain$ctl00$IfcTrimAuditLog$trimAuditLog",
                TrimLogs ? "RadTrimAuditLogYes" : "RadTrimAuditLogNo");

            if (RetentionInDays >= 0)
            {
                PostParameters.Add("ctl00$PlaceHolderMain$ctl00$IfcTrimRetention$TxtTrimRetention",
                    RetentionInDays.ToString(CultureInfo.InvariantCulture));
            }

            PostParameters.Add("ctl00$PlaceHolderMain$ctl00$ctl04$TxtReportStorageLocation", StorageDocumentLibraryName);
            //PostParameters.Add("ctl00$PlaceHolderMain$ctl00$ctl04$TxtReportStorageLocation", "");
            if (EditingItems) { PostParameters.Add("ctl00$PlaceHolderMain$ctl01$ctl01$CheckBoxAuditEdit", "on"); }
            if (CheckinAndCheckout) { PostParameters.Add("ctl00$PlaceHolderMain$ctl01$ctl01$CheckBoxAuditCheckInOut", "on"); }
            if (MovingAndCopying) { PostParameters.Add("ctl00$PlaceHolderMain$ctl01$ctl01$CheckBoxAuditMoveCopy", "on"); }
            if (DeletingAndRestoring) {PostParameters.Add("ctl00$PlaceHolderMain$ctl01$ctl01$CheckBoxAuditDeleteRestore", "on");}
            if (EditContentTypesAndColumns) { PostParameters.Add("ctl00$PlaceHolderMain$ctl02$ctl01$CheckBoxAuditColumnsContentType", "on");}
            if (SearchSiteContent) { PostParameters.Add("ctl00$PlaceHolderMain$ctl02$ctl01$CheckBoxAuditSearch","on"); }
            if (EditUsersAndPermissions) { PostParameters.Add("ctl00$PlaceHolderMain$ctl02$ctl01$CheckBoxAuditPerms", "on"); }
        }

        #endregion

    }
}

and here is the invocation:

 private bool SetSiteAuditing(string oneDriveUrl, string documentLibraryAsRelativeUrl, string adminUserName, string adminPassword)
        {
            try
            {
                // Enable the audit settings
                var auditing = new RequestAuditSettings(oneDriveUrl, AuthenticationType.Office365, adminUserName, adminPassword)
                {
                    TrimLogs = true,
                    RetentionInDays = 30,
                    CheckinAndCheckout = true,
                    MovingAndCopying = true,
                    DeletingAndRestoring = true,
                    EditUsersAndPermissions = true,
                    StorageDocumentLibraryName =  documentLibraryAsRelativeUrl
                };

                auditing.Execute();
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }