Tag Archives: PowerShell

Deploying a SharePoint Add-In from the Catalog via PowerShell

I recently came across a situation where I was asked if I could deploy a SharePoint App/Add-in from the corporate catalog to a sub-site, via PowerShell. With no surprise, there’s no single PowerShell Cmdlet that will perform this task. So, I took it upon myself to reverse engineer the SharePoint storefront and see how Microsoft does it within the platform.

The following code relies on .NET Reflection to invoke private and internal methods in the SharePoint server-side APIs. For this reason, I recommend taking caution in using this code, because we’re calling methods that Microsoft never intended developers to access. I highly recommend keeping this code away from production.

The code assumes the presence of custom add-ins/apps in the catalog and will iterate them. With each add-in/app, you have the option to add the app to the root site of the given site collection. You could easily change this code to suit your purpose. Note: if an app is already installed for a given web, it’ll not show up in the iteration.

[CmdletBinding()]param();

if ((Get-PSSnapin -Name "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) {
    Add-PSSnapin "Microsoft.SharePoint.PowerShell";
}

$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes","Description."
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No","Description."
$cancel = New-Object System.Management.Automation.Host.ChoiceDescription "&Cancel","Description."
$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no, $cancel)

$url = "site collection URL here";
$site = Get-SPSite $url;
$web = $site.RootWeb;

Write-Verbose "Getting apps from the catalog";
$json = Invoke-RestMethod -UseDefaultCredentials -Method Get -Uri $url + "/_layouts/15/addanapp.aspx?task=GetMyApps&sort=1&query=&myappscatalog=0&ci=1&vd=1";
$json | ? { $_.Catalog -eq 1 } | % {
    $appId = $_.ID;

    Write-Host -foreground Yellow "Title: $($_.Title)";
    Write-Host -foreground Yellow "AppID: $appId";

    $result = $host.ui.PromptForChoice("App Install", "Install App $($_.Title)", $options, 1)
    if ($result -eq 2) { break; }
    if ($result -eq 0) {

        Write-Verbose "Get the Corporate Catalog Accessor instance";
        $flags = [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance;
        $asm = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint");
        $ccaType = $asm.GetType("Microsoft.SharePoint.Marketplace.CorporateCuratedGallery.SPCorporateCatalogAccessor");
        $ccaCtor = $ccaType.GetConstructors($flags) | ? { $_.GetParameters().Count -eq 1; }
        $cca = $ccaCtor.Invoke(@($web));

        Write-Verbose "Getting App Package from the Catalog";
        $method = $ccaType.GetMethods($flags) | ? { $_.Name -ilike "GetAppPackage" -and ($_.GetParameters())[0].ParameterType.Name -eq "String" }
        $stream = $method.Invoke($cca, @($appId));

        Write-Verbose "Installing App from Catalog";
        $spAppType = $asm.GetType("Microsoft.SharePoint.Administration.SPApp");
        $method = $spAppType.GetMethod("CreateAppUsingPackageMetadata", [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Static);
        [Microsoft.SharePoint.Administration.SPApp]$spApp = $method.Invoke($null, @($stream, $web, 2, $false, $null, $null));
        $appInstanceId = $spApp.CreateAppInstance($web);
        Write-Host -ForegroundColor Yellow "AppInstanceID: $appInstanceId";
        $appInstance = [Microsoft.SharePoint.Administration.SPAppCatalog]::GetAppInstance($web, $appInstanceId);
        $appInstance.Install();
    }
}

SharePoint 2013 Build Numbers and PowerShell

If you’re in the business of maintaining SharePoint 2013 on-premises, you’ve undoubtedly come across Todd Klindt’s blog post with ongoing table of build numbers and corresponding CU names: http://www.toddklindt.com/sp2013builds.

Knowing the current patch version of your farm is pretty straight forward. You can look up the farm build number in Central Administration -> Manage Servers in Farm, and grab the number at the top of the page. Cross reference this number with the table in Todd’s blog post and you have the CU version installed in your farm.

I wanted to go a step further and write a PowerShell script that pulls the build number and looks up the details from Todd’s blog post automagically. Here it is:

[CmdletBinding()]Param();

$global:srcWebPage = "http://www.toddklindt.com/sp2013builds"; # Thanks Todd.

if ((Get-PSSnapin -Name "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) {
    Add-PSSnapin "Microsoft.SharePoint.PowerShell";
}

try {
    $farm = Get-SPFarm;
    $buildVersion = $farm.BuildVersion;
    $buildVersionString = $buildVersion.ToString();
    $site = Invoke-WebRequest -UseBasicParsing -Uri $global:srcWebPage;
    $pattern = "\<td.*\>.+(" + $buildVersionString.Replace(".", "\.") + ").*\</td\>\s*\<td.*\>(.+)\</td\>\s*\<td.*\>(.+)\</td\>";
    $pattern += '\s*\<td.*\>.*\<a.+href="(.+)".*\>(.+)\</a\>\</td\>';
    $pattern += '\s*\<td.*\>.*\<a.+href="(.+)".*\>(.+)\</a\>\</td\>';
    Write-Verbose $pattern;
    $m = [Regex]::Match($site.RawContent, $pattern, [System.Text.RegularExpressions.RegexOptions]::Multiline);
    if (!$m.Success) { throw "Could not find build number $buildVersionString in $global:srcWebPage"; }
    Write-Host -ForegroundColor white -NoNewline "Current Build Number: ";
    Write-Host -ForegroundColor yellow $buildVersionString;
    Write-Host -ForegroundColor white -NoNewline "Current Patch/CU: ";
    Write-Host -ForegroundColor yellow $m.Groups[2].Value;
    Write-Host -ForegroundColor white -NoNewline "KB of Current Patch/CU: ";
    Write-Host -ForegroundColor yellow $m.Groups[5].Value;
    Write-Host -ForegroundColor white -NoNewline "Download of Current Patch/CU: ";
    Write-Host -ForegroundColor yellow $m.Groups[6].Value;
    Write-Host
    $index = $m.Index + $m.Length;
    $pattern = "\<td.*\>.+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*\</td\>\s*\<td.*\>(.+)\</td\>\s*\<td.*\>(.+)\</td\>";
    $pattern += '\s*\<td.*\>.*\<a.+href="(.+)".*\>(.+)\</a\>\</td\>';
    $pattern += '\s*\<td.*\>.*\<a.+href="(.+)".*\>(.+)\</a\>\</td\>';
    $m = [Regex]::Match($site.RawContent.Substring($index), $pattern, [System.Text.RegularExpressions.RegexOptions]::Multiline);
    if ($m.Success) {
        Write-Host -ForegroundColor white -NoNewline "Next Build Number: ";
        Write-Host -ForegroundColor green $m.Groups[1].Value;
        Write-Host -ForegroundColor white -NoNewline "Next Patch/CU: ";
        Write-Host -ForegroundColor green $m.Groups[2].Value;
        Write-Host -ForegroundColor white -NoNewline "KB of Next Patch/CU: ";
        Write-Host -ForegroundColor green $m.Groups[5].Value;
        Write-Host -ForegroundColor white -NoNewline "Download of Next Patch/CU: ";
        Write-Host -ForegroundColor green $m.Groups[6].Value;
    }

} catch {
    Write-Host -ForegroundColor Red $_.Exception;
}

SharePoint PowerShell Scripts

It’s time to give my blog a fresh injection of content….

I’ve been working for several months on a lot of PowerShell script work for SharePoint (2010, 2013, and SharePoint Online). I figured it was about time that I put sanitized copies of my scripts up on my blog site for all to read.

I’ve added a new section on my blog, aptly named “Scripts”, which you can access via the top level navigation of this site. From there, I present an ongoing set of links to each script I develop and publish. At this time, the following is a list of the client-side scripts I’ve uploaded…

To access my SharePoint 2013 farm provisioning scripts, see here on GitHub.

Bulk Check In for SP2010 Files with No Version Info

SharePoint best practice is to disable “require check in” on document libraries before doing a large bulk import of documents. I received an email from a customer last week, who had not followed this best practice and had over 35,000 documents checked out, which no one but he could see.

Unlike checked out documents with previous check in version(s), a newly uploaded document is not visible by anyone but the person who uploaded the file, even administrators. Fortunately, SharePoint provides a way for an admin to take ownership of these documents via “Manage checked out documents” in the library settings. However, when dealing with a document count that exceeds the default threshold of 10,000, SharePoint returns an error. Temporarily increasing the threshold gets around the error, but then the user interface becomes intolerably slow.

Even after taking ownership, then there’s the task of bulk check in, which is again, a slow process via the UI for large item count document libraries. What I wanted was a PowerShell script to both take ownership of the documents and then check them in. Below is the server-side script I created….

Note: I had to use server-side and not client-side PowerShell because CSOM does not expose checked out files method. The script was tested on SharePoint 2010.

Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue;

function BulkCheckIn {
param([Microsoft.SharePoint.SPFolder]$folder);
$folder.Files | ? { $_.CheckOutStatus -ine "None" } | % {
Write-Host $_.ServerRelativeUrl;
$_.CheckIn("Initial check in", [Microsoft.SharePoint.SPCheckinType]::MajorCheckIn);
}
$folder.SubFolders | % { BulkCheckIn -folder $_; }
}

$site = Get-SPSite http://mysitecollection/;
$web = $site.OpenWeb('subsite/subsite');
$lib = $web.Lists['Documents'];
$lib.CheckedOutFiles | % {
Write-Host "Processing $($_.Url)";
$_.TakeOverCheckOut();
}
BulkCheckIn -folder $lib.RootFolder;